diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index 767d20b98..7d48a8074 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -13,6 +13,7 @@ "xdebug.php-debug", "bmewburn.vscode-intelephense-client", "editorconfig.editorconfig", + "eamodio.gitlens", "github.vscode-pull-request-github", "ms-python.python", "ms-python.flake8", diff --git a/.editorconfig b/.editorconfig index 89a2b9576..c298d1bcd 100644 --- a/.editorconfig +++ b/.editorconfig @@ -1,900 +1,900 @@ -[*] -charset = utf-8 -end_of_line = lf -indent_size = 4 -indent_style = space -insert_final_newline = false -max_line_length = 120 -tab_width = 4 -ij_continuation_indent_size = 8 -ij_formatter_off_tag = @formatter:off -ij_formatter_on_tag = @formatter:on -ij_formatter_tags_enabled = false -ij_smart_tabs = false -ij_visual_guides = none -ij_wrap_on_typing = false - -[*.blade.php] -ij_blade_keep_indents_on_empty_lines = false - -[*.css] -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_block_comment_add_space = false -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 -ij_css_hex_color_upper_case = false -ij_css_keep_blank_lines_in_code = 2 -ij_css_keep_indents_on_empty_lines = false -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_use_double_quotes = true -ij_css_value_alignment = do_not_align - -[*.feature] -indent_size = 2 -ij_gherkin_keep_indents_on_empty_lines = false - -[*.haml] -indent_size = 2 -ij_haml_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_block_comment_add_space = false -ij_less_brace_placement = 0 -ij_less_enforce_quotes_on_format = false -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_line_comment_add_space = false -ij_less_line_comment_at_first_column = 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_use_double_quotes = 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_enforce_quotes_on_format = false -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_line_comment_add_space = false -ij_sass_line_comment_at_first_column = 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_use_double_quotes = true -ij_sass_value_alignment = 0 - -[*.scss] -indent_size = 2 -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_block_comment_add_space = false -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 -ij_scss_hex_color_upper_case = false -ij_scss_keep_blank_lines_in_code = 2 -ij_scss_keep_indents_on_empty_lines = false -ij_scss_keep_single_line_blocks = false -ij_scss_line_comment_add_space = false -ij_scss_line_comment_at_first_column = 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] -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 - -[*.vue] -indent_size = 2 -tab_width = 2 -ij_continuation_indent_size = 4 -ij_vue_indent_children_of_top_level = template -ij_vue_interpolation_new_line_after_start_delimiter = true -ij_vue_interpolation_new_line_before_end_delimiter = true -ij_vue_interpolation_wrap = off -ij_vue_keep_indents_on_empty_lines = false -ij_vue_spaces_within_interpolation_expressions = true - -[.editorconfig] -ij_editorconfig_align_group_field_declarations = false -ij_editorconfig_space_after_colon = false -ij_editorconfig_space_after_comma = true -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}] -ij_xml_align_attributes = true -ij_xml_align_text = false -ij_xml_attribute_wrap = normal -ij_xml_block_comment_add_space = false -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 = false -ij_xml_line_comment_at_first_column = true -ij_xml_space_after_tag_name = false -ij_xml_space_around_equals_in_attribute = false -ij_xml_space_inside_empty_tag = false -ij_xml_text_wrap = normal - -[{*.ats,*.cts,*.mts,*.ts}] -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/**,**/node_modules/**,@angular/material,@angular/material/typings/** -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_block_comment_add_space = false -ij_typescript_block_comment_at_first_column = true -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_enum_constants_wrap = on_every_item -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_explicit_types_function_expression_returns = false -ij_typescript_prefer_explicit_types_function_returns = false -ij_typescript_prefer_explicit_types_vars_fields = 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 = auto -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 - -[{*.bash,*.sh,*.zsh}] -indent_size = 2 -tab_width = 2 -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}] -ij_continuation_indent_size = 4 -ij_javascript_align_imports = false -ij_javascript_align_multiline_array_initializer_expression = false -ij_javascript_align_multiline_binary_operation = false -ij_javascript_align_multiline_chained_methods = false -ij_javascript_align_multiline_extends_list = false -ij_javascript_align_multiline_for = true -ij_javascript_align_multiline_parameters = true -ij_javascript_align_multiline_parameters_in_calls = false -ij_javascript_align_multiline_ternary_operation = false -ij_javascript_align_object_properties = 0 -ij_javascript_align_union_types = false -ij_javascript_align_var_statements = 0 -ij_javascript_array_initializer_new_line_after_left_brace = false -ij_javascript_array_initializer_right_brace_on_new_line = false -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/**,**/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 = end_of_line -ij_javascript_block_comment_add_space = false -ij_javascript_block_comment_at_first_column = true -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 -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 = 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_statement_new_line_after_left_paren = false -ij_javascript_for_statement_right_paren_on_new_line = false -ij_javascript_for_statement_wrap = off -ij_javascript_force_quote_style = false -ij_javascript_force_semicolon_style = false -ij_javascript_function_expression_brace_style = end_of_line -ij_javascript_if_brace_force = never -ij_javascript_import_merge_members = global -ij_javascript_import_prefer_absolute_path = global -ij_javascript_import_sort_members = true -ij_javascript_import_sort_module_name = false -ij_javascript_import_use_node_resolution = true -ij_javascript_imports_wrap = on_every_item -ij_javascript_indent_case_from_switch = true -ij_javascript_indent_chained_calls = true -ij_javascript_indent_package_children = 0 -ij_javascript_jsx_attribute_value = braces -ij_javascript_keep_blank_lines_in_code = 2 -ij_javascript_keep_first_column_comment = true -ij_javascript_keep_indents_on_empty_lines = false -ij_javascript_keep_line_breaks = true -ij_javascript_keep_simple_blocks_in_one_line = false -ij_javascript_keep_simple_methods_in_one_line = false -ij_javascript_line_comment_add_space = true -ij_javascript_line_comment_at_first_column = false -ij_javascript_method_brace_style = end_of_line -ij_javascript_method_call_chain_wrap = off -ij_javascript_method_parameters_new_line_after_left_paren = false -ij_javascript_method_parameters_right_paren_on_new_line = false -ij_javascript_method_parameters_wrap = off -ij_javascript_object_literal_wrap = on_every_item -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 -ij_javascript_space_after_comma = true -ij_javascript_space_after_dots_in_rest_parameter = false -ij_javascript_space_after_generator_mult = true -ij_javascript_space_after_property_colon = true -ij_javascript_space_after_quest = true -ij_javascript_space_after_type_colon = true -ij_javascript_space_after_unary_not = false -ij_javascript_space_before_async_arrow_lparen = true -ij_javascript_space_before_catch_keyword = true -ij_javascript_space_before_catch_left_brace = true -ij_javascript_space_before_catch_parentheses = true -ij_javascript_space_before_class_lbrace = true -ij_javascript_space_before_class_left_brace = true -ij_javascript_space_before_colon = true -ij_javascript_space_before_comma = false -ij_javascript_space_before_do_left_brace = true -ij_javascript_space_before_else_keyword = true -ij_javascript_space_before_else_left_brace = true -ij_javascript_space_before_finally_keyword = true -ij_javascript_space_before_finally_left_brace = true -ij_javascript_space_before_for_left_brace = true -ij_javascript_space_before_for_parentheses = true -ij_javascript_space_before_for_semicolon = false -ij_javascript_space_before_function_left_parenth = true -ij_javascript_space_before_generator_mult = false -ij_javascript_space_before_if_left_brace = true -ij_javascript_space_before_if_parentheses = true -ij_javascript_space_before_method_call_parentheses = false -ij_javascript_space_before_method_left_brace = true -ij_javascript_space_before_method_parentheses = false -ij_javascript_space_before_property_colon = false -ij_javascript_space_before_quest = true -ij_javascript_space_before_switch_left_brace = true -ij_javascript_space_before_switch_parentheses = true -ij_javascript_space_before_try_left_brace = true -ij_javascript_space_before_type_colon = false -ij_javascript_space_before_unary_not = false -ij_javascript_space_before_while_keyword = true -ij_javascript_space_before_while_left_brace = true -ij_javascript_space_before_while_parentheses = true -ij_javascript_spaces_around_additive_operators = true -ij_javascript_spaces_around_arrow_function_operator = true -ij_javascript_spaces_around_assignment_operators = true -ij_javascript_spaces_around_bitwise_operators = true -ij_javascript_spaces_around_equality_operators = true -ij_javascript_spaces_around_logical_operators = true -ij_javascript_spaces_around_multiplicative_operators = true -ij_javascript_spaces_around_relational_operators = true -ij_javascript_spaces_around_shift_operators = true -ij_javascript_spaces_around_unary_operator = false -ij_javascript_spaces_within_array_initializer_brackets = false -ij_javascript_spaces_within_brackets = false -ij_javascript_spaces_within_catch_parentheses = false -ij_javascript_spaces_within_for_parentheses = false -ij_javascript_spaces_within_if_parentheses = false -ij_javascript_spaces_within_imports = false -ij_javascript_spaces_within_interpolation_expressions = false -ij_javascript_spaces_within_method_call_parentheses = false -ij_javascript_spaces_within_method_parentheses = false -ij_javascript_spaces_within_object_literal_braces = false -ij_javascript_spaces_within_object_type_braces = true -ij_javascript_spaces_within_parentheses = false -ij_javascript_spaces_within_switch_parentheses = false -ij_javascript_spaces_within_type_assertion = false -ij_javascript_spaces_within_union_types = true -ij_javascript_spaces_within_while_parentheses = false -ij_javascript_special_else_if_treatment = true -ij_javascript_ternary_operation_signs_on_next_line = false -ij_javascript_ternary_operation_wrap = off -ij_javascript_union_types_wrap = on_every_item -ij_javascript_use_chained_calls_group_indents = false -ij_javascript_use_double_quotes = true -ij_javascript_use_explicit_js_extension = auto -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_on_new_line = false -ij_javascript_wrap_comments = false - -[{*.cjsx,*.coffee}] -indent_size = 2 -tab_width = 2 -ij_continuation_indent_size = 2 -ij_coffeescript_align_function_body = false -ij_coffeescript_align_imports = false -ij_coffeescript_align_multiline_array_initializer_expression = true -ij_coffeescript_align_multiline_parameters = true -ij_coffeescript_align_multiline_parameters_in_calls = false -ij_coffeescript_align_object_properties = 0 -ij_coffeescript_align_union_types = false -ij_coffeescript_align_var_statements = 0 -ij_coffeescript_array_initializer_new_line_after_left_brace = false -ij_coffeescript_array_initializer_right_brace_on_new_line = false -ij_coffeescript_array_initializer_wrap = normal -ij_coffeescript_blacklist_imports = rxjs/Rx,node_modules/**,**/node_modules/**,@angular/material,@angular/material/typings/** -ij_coffeescript_blank_lines_around_function = 1 -ij_coffeescript_call_parameters_new_line_after_left_paren = false -ij_coffeescript_call_parameters_right_paren_on_new_line = false -ij_coffeescript_call_parameters_wrap = normal -ij_coffeescript_chained_call_dot_on_new_line = true -ij_coffeescript_comma_on_new_line = false -ij_coffeescript_enforce_trailing_comma = keep -ij_coffeescript_field_prefix = _ -ij_coffeescript_file_name_style = relaxed -ij_coffeescript_force_quote_style = false -ij_coffeescript_force_semicolon_style = false -ij_coffeescript_function_expression_brace_style = end_of_line -ij_coffeescript_import_merge_members = global -ij_coffeescript_import_prefer_absolute_path = global -ij_coffeescript_import_sort_members = true -ij_coffeescript_import_sort_module_name = false -ij_coffeescript_import_use_node_resolution = true -ij_coffeescript_imports_wrap = on_every_item -ij_coffeescript_indent_chained_calls = true -ij_coffeescript_indent_package_children = 0 -ij_coffeescript_jsx_attribute_value = braces -ij_coffeescript_keep_blank_lines_in_code = 2 -ij_coffeescript_keep_first_column_comment = true -ij_coffeescript_keep_indents_on_empty_lines = false -ij_coffeescript_keep_line_breaks = true -ij_coffeescript_keep_simple_methods_in_one_line = false -ij_coffeescript_method_parameters_new_line_after_left_paren = false -ij_coffeescript_method_parameters_right_paren_on_new_line = false -ij_coffeescript_method_parameters_wrap = off -ij_coffeescript_object_literal_wrap = on_every_item -ij_coffeescript_prefer_as_type_cast = false -ij_coffeescript_prefer_explicit_types_function_expression_returns = false -ij_coffeescript_prefer_explicit_types_function_returns = false -ij_coffeescript_prefer_explicit_types_vars_fields = false -ij_coffeescript_reformat_c_style_comments = false -ij_coffeescript_space_after_comma = true -ij_coffeescript_space_after_dots_in_rest_parameter = false -ij_coffeescript_space_after_generator_mult = true -ij_coffeescript_space_after_property_colon = true -ij_coffeescript_space_after_type_colon = true -ij_coffeescript_space_after_unary_not = false -ij_coffeescript_space_before_async_arrow_lparen = true -ij_coffeescript_space_before_class_lbrace = true -ij_coffeescript_space_before_comma = false -ij_coffeescript_space_before_function_left_parenth = true -ij_coffeescript_space_before_generator_mult = false -ij_coffeescript_space_before_property_colon = false -ij_coffeescript_space_before_type_colon = false -ij_coffeescript_space_before_unary_not = false -ij_coffeescript_spaces_around_additive_operators = true -ij_coffeescript_spaces_around_arrow_function_operator = true -ij_coffeescript_spaces_around_assignment_operators = true -ij_coffeescript_spaces_around_bitwise_operators = true -ij_coffeescript_spaces_around_equality_operators = true -ij_coffeescript_spaces_around_logical_operators = true -ij_coffeescript_spaces_around_multiplicative_operators = true -ij_coffeescript_spaces_around_relational_operators = true -ij_coffeescript_spaces_around_shift_operators = true -ij_coffeescript_spaces_around_unary_operator = false -ij_coffeescript_spaces_within_array_initializer_braces = false -ij_coffeescript_spaces_within_array_initializer_brackets = false -ij_coffeescript_spaces_within_imports = false -ij_coffeescript_spaces_within_index_brackets = false -ij_coffeescript_spaces_within_interpolation_expressions = false -ij_coffeescript_spaces_within_method_call_parentheses = false -ij_coffeescript_spaces_within_method_parentheses = false -ij_coffeescript_spaces_within_object_braces = false -ij_coffeescript_spaces_within_object_literal_braces = false -ij_coffeescript_spaces_within_object_type_braces = true -ij_coffeescript_spaces_within_range_brackets = false -ij_coffeescript_spaces_within_type_assertion = false -ij_coffeescript_spaces_within_union_types = true -ij_coffeescript_union_types_wrap = on_every_item -ij_coffeescript_use_chained_calls_group_indents = false -ij_coffeescript_use_double_quotes = true -ij_coffeescript_use_explicit_js_extension = auto -ij_coffeescript_use_path_mapping = always -ij_coffeescript_use_public_modifier = false -ij_coffeescript_use_semicolon_after_statement = false -ij_coffeescript_var_declaration_wrap = normal - -[{*.ctp,*.hphp,*.inc,*.module,*.php,*.php4,*.php5,*.phtml}] -indent_size = 2 -tab_width = 2 -ij_continuation_indent_size = 2 -ij_php_align_assignments = false -ij_php_align_class_constants = true -ij_php_align_group_field_declarations = true -ij_php_align_inline_comments = false -ij_php_align_key_value_pairs = false -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 -ij_php_align_multiline_for = false -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 = 28 -ij_php_array_initializer_new_line_after_left_brace = false -ij_php_array_initializer_right_brace_on_new_line = true -ij_php_array_initializer_wrap = off -ij_php_assignment_wrap = off -ij_php_attributes_wrap = off -ij_php_author_weight = 28 -ij_php_binary_operation_sign_on_next_line = false -ij_php_binary_operation_wrap = off -ij_php_blank_lines_after_class_header = 0 -ij_php_blank_lines_after_function = 1 -ij_php_blank_lines_after_imports = 1 -ij_php_blank_lines_after_opening_tag = 0 -ij_php_blank_lines_after_package = 0 -ij_php_blank_lines_around_class = 1 -ij_php_blank_lines_around_constants = 0 -ij_php_blank_lines_around_field = 0 -ij_php_blank_lines_around_method = 1 -ij_php_blank_lines_before_class_end = 0 -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 = 0 -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 = true -ij_php_call_parameters_wrap = off -ij_php_catch_on_new_line = true -ij_php_category_weight = 28 -ij_php_class_brace_style = end_of_line -ij_php_comma_after_last_array_element = false -ij_php_concat_spaces = true -ij_php_copyright_weight = 28 -ij_php_deprecated_weight = 28 -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 = 28 -ij_php_extends_keyword_wrap = off -ij_php_extends_list_wrap = off -ij_php_fields_default_visibility = private -ij_php_filesource_weight = 28 -ij_php_finally_on_new_line = true -ij_php_for_brace_force = always -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_empty_methods_in_one_line = false -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 -ij_php_if_lparen_on_next_line = false -ij_php_if_rparen_on_next_line = false -ij_php_ignore_weight = 28 -ij_php_import_sorting = alphabetic -ij_php_indent_break_from_case = true -ij_php_indent_case_from_switch = true -ij_php_indent_code_in_php_tags = false -ij_php_internal_weight = 28 -ij_php_keep_blank_lines_after_lbrace = 2 -ij_php_keep_blank_lines_before_right_brace = 2 -ij_php_keep_blank_lines_in_code = 2 -ij_php_keep_blank_lines_in_declarations = 2 -ij_php_keep_control_statement_in_one_line = true -ij_php_keep_first_column_comment = true -ij_php_keep_indents_on_empty_lines = true -ij_php_keep_line_breaks = true -ij_php_keep_rparen_and_lbrace_on_one_line = true -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 -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 = false -ij_php_lower_case_keywords = true -ij_php_lower_case_null_const = false -ij_php_method_brace_style = end_of_line -ij_php_method_call_chain_wrap = off -ij_php_method_parameters_new_line_after_left_paren = false -ij_php_method_parameters_right_paren_on_new_line = false -ij_php_method_parameters_wrap = off -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 = 0 -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 = false -ij_php_phpdoc_blank_lines_around_parameters = false -ij_php_phpdoc_keep_blank_lines = true -ij_php_phpdoc_param_spaces_between_name_and_description = 1 -ij_php_phpdoc_param_spaces_between_tag_and_type = 1 -ij_php_phpdoc_param_spaces_between_type_and_name = 1 -ij_php_phpdoc_use_fqcn = false -ij_php_phpdoc_wrap_long_lines = false -ij_php_place_assignment_sign_on_next_line = false -ij_php_place_parens_for_constructor = 0 -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 = 1 -ij_php_see_weight = 28 -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 -ij_php_space_after_quest = true -ij_php_space_after_type_cast = false -ij_php_space_after_unary_not = false -ij_php_space_before_array_initializer_left_brace = false -ij_php_space_before_catch_keyword = true -ij_php_space_before_catch_left_brace = true -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 -ij_php_space_before_else_keyword = true -ij_php_space_before_else_left_brace = true -ij_php_space_before_finally_keyword = true -ij_php_space_before_finally_left_brace = true -ij_php_space_before_for_left_brace = true -ij_php_space_before_for_parentheses = true -ij_php_space_before_for_semicolon = false -ij_php_space_before_if_left_brace = true -ij_php_space_before_if_parentheses = true -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 -ij_php_space_before_unary_not = false -ij_php_space_before_while_keyword = true -ij_php_space_before_while_left_brace = true -ij_php_space_before_while_parentheses = true -ij_php_space_between_ternary_quest_and_colon = false -ij_php_spaces_around_additive_operators = true -ij_php_spaces_around_arrow = false -ij_php_spaces_around_assignment_in_declare = false -ij_php_spaces_around_assignment_operators = true -ij_php_spaces_around_bitwise_operators = true -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 -ij_php_spaces_around_var_within_brackets = false -ij_php_spaces_within_array_initializer_braces = false -ij_php_spaces_within_brackets = false -ij_php_spaces_within_catch_parentheses = false -ij_php_spaces_within_for_parentheses = false -ij_php_spaces_within_if_parentheses = false -ij_php_spaces_within_method_call_parentheses = false -ij_php_spaces_within_method_parentheses = false -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 = 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 = 2 -ij_php_todo_weight = 28 -ij_php_treat_multiline_arrays_and_lambdas_multiline = false -ij_php_unknown_tag_weight = 28 -ij_php_upper_case_boolean_const = false -ij_php_upper_case_null_const = false -ij_php_uses_weight = 28 -ij_php_var_weight = 28 -ij_php_variable_naming_style = mixed -ij_php_version_weight = 28 -ij_php_while_brace_force = always -ij_php_while_on_new_line = false - -[{*.har,*.jsb2,*.jsb3,*.json,.babelrc,.eslintrc,.stylelintrc,bowerrc,composer.lock,jest.config}] -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 - -[{*.htm,*.html,*.ng,*.sht,*.shtm,*.shtml}] -indent_size = 2 -tab_width = 2 -ij_continuation_indent_size = 2 -ij_html_add_new_line_before_tags = body,div,p,form,h1,h2,h3 -ij_html_align_attributes = true -ij_html_align_text = false -ij_html_attribute_wrap = normal -ij_html_block_comment_add_space = false -ij_html_block_comment_at_first_column = true -ij_html_do_not_align_children_of_min_lines = 0 -ij_html_do_not_break_if_inline_tags = title,h1,h2,h3,h4,h5,h6,p -ij_html_do_not_indent_children_of_tags = html,body,thead,tbody,tfoot -ij_html_enforce_quotes = false -ij_html_inline_tags = a,abbr,acronym,b,basefont,bdo,big,br,cite,cite,code,dfn,em,font,i,img,input,kbd,label,q,s,samp,select,small,span,strike,strong,sub,sup,textarea,tt,u,var -ij_html_keep_blank_lines = 2 -ij_html_keep_indents_on_empty_lines = false -ij_html_keep_line_breaks = true -ij_html_keep_line_breaks_in_text = true -ij_html_keep_whitespaces = false -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_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 - -[{*.markdown,*.md}] -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_insert_quote_arrows_on_wrap = true -ij_markdown_keep_indents_on_empty_lines = false -ij_markdown_keep_line_breaks_inside_text_blocks = true -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 -ij_markdown_wrap_text_if_long = true -ij_markdown_wrap_text_inside_blockquotes = true - -[{*.yaml,*.yml}] -indent_size = 2 -ij_yaml_align_values_properties = do_not_align -ij_yaml_autoinsert_sequence_marker = true -ij_yaml_block_mapping_on_new_line = false -ij_yaml_indent_sequence_value = true -ij_yaml_keep_indents_on_empty_lines = false -ij_yaml_keep_line_breaks = true -ij_yaml_sequence_on_new_line = false -ij_yaml_space_before_colon = false -ij_yaml_spaces_within_braces = true -ij_yaml_spaces_within_brackets = true +[*] +charset = utf-8 +end_of_line = lf +indent_size = 4 +indent_style = space +insert_final_newline = false +max_line_length = 120 +tab_width = 4 +ij_continuation_indent_size = 8 +ij_formatter_off_tag = @formatter:off +ij_formatter_on_tag = @formatter:on +ij_formatter_tags_enabled = false +ij_smart_tabs = false +ij_visual_guides = none +ij_wrap_on_typing = false + +[*.blade.php] +ij_blade_keep_indents_on_empty_lines = false + +[*.css] +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_block_comment_add_space = false +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 +ij_css_hex_color_upper_case = false +ij_css_keep_blank_lines_in_code = 2 +ij_css_keep_indents_on_empty_lines = false +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_use_double_quotes = true +ij_css_value_alignment = do_not_align + +[*.feature] +indent_size = 2 +ij_gherkin_keep_indents_on_empty_lines = false + +[*.haml] +indent_size = 2 +ij_haml_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_block_comment_add_space = false +ij_less_brace_placement = 0 +ij_less_enforce_quotes_on_format = false +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_line_comment_add_space = false +ij_less_line_comment_at_first_column = 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_use_double_quotes = 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_enforce_quotes_on_format = false +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_line_comment_add_space = false +ij_sass_line_comment_at_first_column = 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_use_double_quotes = true +ij_sass_value_alignment = 0 + +[*.scss] +indent_size = 2 +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_block_comment_add_space = false +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 +ij_scss_hex_color_upper_case = false +ij_scss_keep_blank_lines_in_code = 2 +ij_scss_keep_indents_on_empty_lines = false +ij_scss_keep_single_line_blocks = false +ij_scss_line_comment_add_space = false +ij_scss_line_comment_at_first_column = 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] +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 + +[*.vue] +indent_size = 2 +tab_width = 2 +ij_continuation_indent_size = 4 +ij_vue_indent_children_of_top_level = template +ij_vue_interpolation_new_line_after_start_delimiter = true +ij_vue_interpolation_new_line_before_end_delimiter = true +ij_vue_interpolation_wrap = off +ij_vue_keep_indents_on_empty_lines = false +ij_vue_spaces_within_interpolation_expressions = true + +[.editorconfig] +ij_editorconfig_align_group_field_declarations = false +ij_editorconfig_space_after_colon = false +ij_editorconfig_space_after_comma = true +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}] +ij_xml_align_attributes = true +ij_xml_align_text = false +ij_xml_attribute_wrap = normal +ij_xml_block_comment_add_space = false +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 = false +ij_xml_line_comment_at_first_column = true +ij_xml_space_after_tag_name = false +ij_xml_space_around_equals_in_attribute = false +ij_xml_space_inside_empty_tag = false +ij_xml_text_wrap = normal + +[{*.ats,*.cts,*.mts,*.ts}] +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/**,**/node_modules/**,@angular/material,@angular/material/typings/** +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_block_comment_add_space = false +ij_typescript_block_comment_at_first_column = true +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_enum_constants_wrap = on_every_item +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_explicit_types_function_expression_returns = false +ij_typescript_prefer_explicit_types_function_returns = false +ij_typescript_prefer_explicit_types_vars_fields = 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 = auto +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 + +[{*.bash,*.sh,*.zsh}] +indent_size = 2 +tab_width = 2 +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}] +ij_continuation_indent_size = 4 +ij_javascript_align_imports = false +ij_javascript_align_multiline_array_initializer_expression = false +ij_javascript_align_multiline_binary_operation = false +ij_javascript_align_multiline_chained_methods = false +ij_javascript_align_multiline_extends_list = false +ij_javascript_align_multiline_for = true +ij_javascript_align_multiline_parameters = true +ij_javascript_align_multiline_parameters_in_calls = false +ij_javascript_align_multiline_ternary_operation = false +ij_javascript_align_object_properties = 0 +ij_javascript_align_union_types = false +ij_javascript_align_var_statements = 0 +ij_javascript_array_initializer_new_line_after_left_brace = false +ij_javascript_array_initializer_right_brace_on_new_line = false +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/**,**/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 = end_of_line +ij_javascript_block_comment_add_space = false +ij_javascript_block_comment_at_first_column = true +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 +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 = 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_statement_new_line_after_left_paren = false +ij_javascript_for_statement_right_paren_on_new_line = false +ij_javascript_for_statement_wrap = off +ij_javascript_force_quote_style = false +ij_javascript_force_semicolon_style = false +ij_javascript_function_expression_brace_style = end_of_line +ij_javascript_if_brace_force = never +ij_javascript_import_merge_members = global +ij_javascript_import_prefer_absolute_path = global +ij_javascript_import_sort_members = true +ij_javascript_import_sort_module_name = false +ij_javascript_import_use_node_resolution = true +ij_javascript_imports_wrap = on_every_item +ij_javascript_indent_case_from_switch = true +ij_javascript_indent_chained_calls = true +ij_javascript_indent_package_children = 0 +ij_javascript_jsx_attribute_value = braces +ij_javascript_keep_blank_lines_in_code = 2 +ij_javascript_keep_first_column_comment = true +ij_javascript_keep_indents_on_empty_lines = false +ij_javascript_keep_line_breaks = true +ij_javascript_keep_simple_blocks_in_one_line = false +ij_javascript_keep_simple_methods_in_one_line = false +ij_javascript_line_comment_add_space = true +ij_javascript_line_comment_at_first_column = false +ij_javascript_method_brace_style = end_of_line +ij_javascript_method_call_chain_wrap = off +ij_javascript_method_parameters_new_line_after_left_paren = false +ij_javascript_method_parameters_right_paren_on_new_line = false +ij_javascript_method_parameters_wrap = off +ij_javascript_object_literal_wrap = on_every_item +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 +ij_javascript_space_after_comma = true +ij_javascript_space_after_dots_in_rest_parameter = false +ij_javascript_space_after_generator_mult = true +ij_javascript_space_after_property_colon = true +ij_javascript_space_after_quest = true +ij_javascript_space_after_type_colon = true +ij_javascript_space_after_unary_not = false +ij_javascript_space_before_async_arrow_lparen = true +ij_javascript_space_before_catch_keyword = true +ij_javascript_space_before_catch_left_brace = true +ij_javascript_space_before_catch_parentheses = true +ij_javascript_space_before_class_lbrace = true +ij_javascript_space_before_class_left_brace = true +ij_javascript_space_before_colon = true +ij_javascript_space_before_comma = false +ij_javascript_space_before_do_left_brace = true +ij_javascript_space_before_else_keyword = true +ij_javascript_space_before_else_left_brace = true +ij_javascript_space_before_finally_keyword = true +ij_javascript_space_before_finally_left_brace = true +ij_javascript_space_before_for_left_brace = true +ij_javascript_space_before_for_parentheses = true +ij_javascript_space_before_for_semicolon = false +ij_javascript_space_before_function_left_parenth = true +ij_javascript_space_before_generator_mult = false +ij_javascript_space_before_if_left_brace = true +ij_javascript_space_before_if_parentheses = true +ij_javascript_space_before_method_call_parentheses = false +ij_javascript_space_before_method_left_brace = true +ij_javascript_space_before_method_parentheses = false +ij_javascript_space_before_property_colon = false +ij_javascript_space_before_quest = true +ij_javascript_space_before_switch_left_brace = true +ij_javascript_space_before_switch_parentheses = true +ij_javascript_space_before_try_left_brace = true +ij_javascript_space_before_type_colon = false +ij_javascript_space_before_unary_not = false +ij_javascript_space_before_while_keyword = true +ij_javascript_space_before_while_left_brace = true +ij_javascript_space_before_while_parentheses = true +ij_javascript_spaces_around_additive_operators = true +ij_javascript_spaces_around_arrow_function_operator = true +ij_javascript_spaces_around_assignment_operators = true +ij_javascript_spaces_around_bitwise_operators = true +ij_javascript_spaces_around_equality_operators = true +ij_javascript_spaces_around_logical_operators = true +ij_javascript_spaces_around_multiplicative_operators = true +ij_javascript_spaces_around_relational_operators = true +ij_javascript_spaces_around_shift_operators = true +ij_javascript_spaces_around_unary_operator = false +ij_javascript_spaces_within_array_initializer_brackets = false +ij_javascript_spaces_within_brackets = false +ij_javascript_spaces_within_catch_parentheses = false +ij_javascript_spaces_within_for_parentheses = false +ij_javascript_spaces_within_if_parentheses = false +ij_javascript_spaces_within_imports = false +ij_javascript_spaces_within_interpolation_expressions = false +ij_javascript_spaces_within_method_call_parentheses = false +ij_javascript_spaces_within_method_parentheses = false +ij_javascript_spaces_within_object_literal_braces = false +ij_javascript_spaces_within_object_type_braces = true +ij_javascript_spaces_within_parentheses = false +ij_javascript_spaces_within_switch_parentheses = false +ij_javascript_spaces_within_type_assertion = false +ij_javascript_spaces_within_union_types = true +ij_javascript_spaces_within_while_parentheses = false +ij_javascript_special_else_if_treatment = true +ij_javascript_ternary_operation_signs_on_next_line = false +ij_javascript_ternary_operation_wrap = off +ij_javascript_union_types_wrap = on_every_item +ij_javascript_use_chained_calls_group_indents = false +ij_javascript_use_double_quotes = true +ij_javascript_use_explicit_js_extension = auto +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_on_new_line = false +ij_javascript_wrap_comments = false + +[{*.cjsx,*.coffee}] +indent_size = 2 +tab_width = 2 +ij_continuation_indent_size = 2 +ij_coffeescript_align_function_body = false +ij_coffeescript_align_imports = false +ij_coffeescript_align_multiline_array_initializer_expression = true +ij_coffeescript_align_multiline_parameters = true +ij_coffeescript_align_multiline_parameters_in_calls = false +ij_coffeescript_align_object_properties = 0 +ij_coffeescript_align_union_types = false +ij_coffeescript_align_var_statements = 0 +ij_coffeescript_array_initializer_new_line_after_left_brace = false +ij_coffeescript_array_initializer_right_brace_on_new_line = false +ij_coffeescript_array_initializer_wrap = normal +ij_coffeescript_blacklist_imports = rxjs/Rx,node_modules/**,**/node_modules/**,@angular/material,@angular/material/typings/** +ij_coffeescript_blank_lines_around_function = 1 +ij_coffeescript_call_parameters_new_line_after_left_paren = false +ij_coffeescript_call_parameters_right_paren_on_new_line = false +ij_coffeescript_call_parameters_wrap = normal +ij_coffeescript_chained_call_dot_on_new_line = true +ij_coffeescript_comma_on_new_line = false +ij_coffeescript_enforce_trailing_comma = keep +ij_coffeescript_field_prefix = _ +ij_coffeescript_file_name_style = relaxed +ij_coffeescript_force_quote_style = false +ij_coffeescript_force_semicolon_style = false +ij_coffeescript_function_expression_brace_style = end_of_line +ij_coffeescript_import_merge_members = global +ij_coffeescript_import_prefer_absolute_path = global +ij_coffeescript_import_sort_members = true +ij_coffeescript_import_sort_module_name = false +ij_coffeescript_import_use_node_resolution = true +ij_coffeescript_imports_wrap = on_every_item +ij_coffeescript_indent_chained_calls = true +ij_coffeescript_indent_package_children = 0 +ij_coffeescript_jsx_attribute_value = braces +ij_coffeescript_keep_blank_lines_in_code = 2 +ij_coffeescript_keep_first_column_comment = true +ij_coffeescript_keep_indents_on_empty_lines = false +ij_coffeescript_keep_line_breaks = true +ij_coffeescript_keep_simple_methods_in_one_line = false +ij_coffeescript_method_parameters_new_line_after_left_paren = false +ij_coffeescript_method_parameters_right_paren_on_new_line = false +ij_coffeescript_method_parameters_wrap = off +ij_coffeescript_object_literal_wrap = on_every_item +ij_coffeescript_prefer_as_type_cast = false +ij_coffeescript_prefer_explicit_types_function_expression_returns = false +ij_coffeescript_prefer_explicit_types_function_returns = false +ij_coffeescript_prefer_explicit_types_vars_fields = false +ij_coffeescript_reformat_c_style_comments = false +ij_coffeescript_space_after_comma = true +ij_coffeescript_space_after_dots_in_rest_parameter = false +ij_coffeescript_space_after_generator_mult = true +ij_coffeescript_space_after_property_colon = true +ij_coffeescript_space_after_type_colon = true +ij_coffeescript_space_after_unary_not = false +ij_coffeescript_space_before_async_arrow_lparen = true +ij_coffeescript_space_before_class_lbrace = true +ij_coffeescript_space_before_comma = false +ij_coffeescript_space_before_function_left_parenth = true +ij_coffeescript_space_before_generator_mult = false +ij_coffeescript_space_before_property_colon = false +ij_coffeescript_space_before_type_colon = false +ij_coffeescript_space_before_unary_not = false +ij_coffeescript_spaces_around_additive_operators = true +ij_coffeescript_spaces_around_arrow_function_operator = true +ij_coffeescript_spaces_around_assignment_operators = true +ij_coffeescript_spaces_around_bitwise_operators = true +ij_coffeescript_spaces_around_equality_operators = true +ij_coffeescript_spaces_around_logical_operators = true +ij_coffeescript_spaces_around_multiplicative_operators = true +ij_coffeescript_spaces_around_relational_operators = true +ij_coffeescript_spaces_around_shift_operators = true +ij_coffeescript_spaces_around_unary_operator = false +ij_coffeescript_spaces_within_array_initializer_braces = false +ij_coffeescript_spaces_within_array_initializer_brackets = false +ij_coffeescript_spaces_within_imports = false +ij_coffeescript_spaces_within_index_brackets = false +ij_coffeescript_spaces_within_interpolation_expressions = false +ij_coffeescript_spaces_within_method_call_parentheses = false +ij_coffeescript_spaces_within_method_parentheses = false +ij_coffeescript_spaces_within_object_braces = false +ij_coffeescript_spaces_within_object_literal_braces = false +ij_coffeescript_spaces_within_object_type_braces = true +ij_coffeescript_spaces_within_range_brackets = false +ij_coffeescript_spaces_within_type_assertion = false +ij_coffeescript_spaces_within_union_types = true +ij_coffeescript_union_types_wrap = on_every_item +ij_coffeescript_use_chained_calls_group_indents = false +ij_coffeescript_use_double_quotes = true +ij_coffeescript_use_explicit_js_extension = auto +ij_coffeescript_use_path_mapping = always +ij_coffeescript_use_public_modifier = false +ij_coffeescript_use_semicolon_after_statement = false +ij_coffeescript_var_declaration_wrap = normal + +[{*.ctp,*.hphp,*.inc,*.module,*.php,*.php4,*.php5,*.phtml}] +indent_size = 2 +tab_width = 2 +ij_continuation_indent_size = 2 +ij_php_align_assignments = false +ij_php_align_class_constants = true +ij_php_align_group_field_declarations = true +ij_php_align_inline_comments = false +ij_php_align_key_value_pairs = false +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 +ij_php_align_multiline_for = false +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 = 28 +ij_php_array_initializer_new_line_after_left_brace = false +ij_php_array_initializer_right_brace_on_new_line = true +ij_php_array_initializer_wrap = off +ij_php_assignment_wrap = off +ij_php_attributes_wrap = off +ij_php_author_weight = 28 +ij_php_binary_operation_sign_on_next_line = false +ij_php_binary_operation_wrap = off +ij_php_blank_lines_after_class_header = 0 +ij_php_blank_lines_after_function = 1 +ij_php_blank_lines_after_imports = 1 +ij_php_blank_lines_after_opening_tag = 0 +ij_php_blank_lines_after_package = 0 +ij_php_blank_lines_around_class = 1 +ij_php_blank_lines_around_constants = 0 +ij_php_blank_lines_around_field = 0 +ij_php_blank_lines_around_method = 1 +ij_php_blank_lines_before_class_end = 0 +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 = 0 +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 = true +ij_php_call_parameters_wrap = off +ij_php_catch_on_new_line = true +ij_php_category_weight = 28 +ij_php_class_brace_style = end_of_line +ij_php_comma_after_last_array_element = false +ij_php_concat_spaces = true +ij_php_copyright_weight = 28 +ij_php_deprecated_weight = 28 +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 = 28 +ij_php_extends_keyword_wrap = off +ij_php_extends_list_wrap = off +ij_php_fields_default_visibility = private +ij_php_filesource_weight = 28 +ij_php_finally_on_new_line = true +ij_php_for_brace_force = always +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_empty_methods_in_one_line = false +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 +ij_php_if_lparen_on_next_line = false +ij_php_if_rparen_on_next_line = false +ij_php_ignore_weight = 28 +ij_php_import_sorting = alphabetic +ij_php_indent_break_from_case = true +ij_php_indent_case_from_switch = true +ij_php_indent_code_in_php_tags = false +ij_php_internal_weight = 28 +ij_php_keep_blank_lines_after_lbrace = 2 +ij_php_keep_blank_lines_before_right_brace = 2 +ij_php_keep_blank_lines_in_code = 2 +ij_php_keep_blank_lines_in_declarations = 2 +ij_php_keep_control_statement_in_one_line = true +ij_php_keep_first_column_comment = true +ij_php_keep_indents_on_empty_lines = true +ij_php_keep_line_breaks = true +ij_php_keep_rparen_and_lbrace_on_one_line = true +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 +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 = false +ij_php_lower_case_keywords = true +ij_php_lower_case_null_const = false +ij_php_method_brace_style = end_of_line +ij_php_method_call_chain_wrap = off +ij_php_method_parameters_new_line_after_left_paren = false +ij_php_method_parameters_right_paren_on_new_line = false +ij_php_method_parameters_wrap = off +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 = 0 +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 = false +ij_php_phpdoc_blank_lines_around_parameters = false +ij_php_phpdoc_keep_blank_lines = true +ij_php_phpdoc_param_spaces_between_name_and_description = 1 +ij_php_phpdoc_param_spaces_between_tag_and_type = 1 +ij_php_phpdoc_param_spaces_between_type_and_name = 1 +ij_php_phpdoc_use_fqcn = false +ij_php_phpdoc_wrap_long_lines = false +ij_php_place_assignment_sign_on_next_line = false +ij_php_place_parens_for_constructor = 0 +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 = 1 +ij_php_see_weight = 28 +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 +ij_php_space_after_quest = true +ij_php_space_after_type_cast = false +ij_php_space_after_unary_not = false +ij_php_space_before_array_initializer_left_brace = false +ij_php_space_before_catch_keyword = true +ij_php_space_before_catch_left_brace = true +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 +ij_php_space_before_else_keyword = true +ij_php_space_before_else_left_brace = true +ij_php_space_before_finally_keyword = true +ij_php_space_before_finally_left_brace = true +ij_php_space_before_for_left_brace = true +ij_php_space_before_for_parentheses = true +ij_php_space_before_for_semicolon = false +ij_php_space_before_if_left_brace = true +ij_php_space_before_if_parentheses = true +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 +ij_php_space_before_unary_not = false +ij_php_space_before_while_keyword = true +ij_php_space_before_while_left_brace = true +ij_php_space_before_while_parentheses = true +ij_php_space_between_ternary_quest_and_colon = false +ij_php_spaces_around_additive_operators = true +ij_php_spaces_around_arrow = false +ij_php_spaces_around_assignment_in_declare = false +ij_php_spaces_around_assignment_operators = true +ij_php_spaces_around_bitwise_operators = true +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 +ij_php_spaces_around_var_within_brackets = false +ij_php_spaces_within_array_initializer_braces = false +ij_php_spaces_within_brackets = false +ij_php_spaces_within_catch_parentheses = false +ij_php_spaces_within_for_parentheses = false +ij_php_spaces_within_if_parentheses = false +ij_php_spaces_within_method_call_parentheses = false +ij_php_spaces_within_method_parentheses = false +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 = 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 = 2 +ij_php_todo_weight = 28 +ij_php_treat_multiline_arrays_and_lambdas_multiline = false +ij_php_unknown_tag_weight = 28 +ij_php_upper_case_boolean_const = false +ij_php_upper_case_null_const = false +ij_php_uses_weight = 28 +ij_php_var_weight = 28 +ij_php_variable_naming_style = mixed +ij_php_version_weight = 28 +ij_php_while_brace_force = always +ij_php_while_on_new_line = false + +[{*.har,*.jsb2,*.jsb3,*.json,.babelrc,.eslintrc,.stylelintrc,bowerrc,composer.lock,jest.config}] +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 + +[{*.htm,*.html,*.ng,*.sht,*.shtm,*.shtml}] +indent_size = 2 +tab_width = 2 +ij_continuation_indent_size = 2 +ij_html_add_new_line_before_tags = body,div,p,form,h1,h2,h3 +ij_html_align_attributes = true +ij_html_align_text = false +ij_html_attribute_wrap = normal +ij_html_block_comment_add_space = false +ij_html_block_comment_at_first_column = true +ij_html_do_not_align_children_of_min_lines = 0 +ij_html_do_not_break_if_inline_tags = title,h1,h2,h3,h4,h5,h6,p +ij_html_do_not_indent_children_of_tags = html,body,thead,tbody,tfoot +ij_html_enforce_quotes = false +ij_html_inline_tags = a,abbr,acronym,b,basefont,bdo,big,br,cite,cite,code,dfn,em,font,i,img,input,kbd,label,q,s,samp,select,small,span,strike,strong,sub,sup,textarea,tt,u,var +ij_html_keep_blank_lines = 2 +ij_html_keep_indents_on_empty_lines = false +ij_html_keep_line_breaks = true +ij_html_keep_line_breaks_in_text = true +ij_html_keep_whitespaces = false +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_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 + +[{*.markdown,*.md}] +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_insert_quote_arrows_on_wrap = true +ij_markdown_keep_indents_on_empty_lines = false +ij_markdown_keep_line_breaks_inside_text_blocks = true +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 +ij_markdown_wrap_text_if_long = true +ij_markdown_wrap_text_inside_blockquotes = true + +[{*.yaml,*.yml}] +indent_size = 2 +ij_yaml_align_values_properties = do_not_align +ij_yaml_autoinsert_sequence_marker = true +ij_yaml_block_mapping_on_new_line = false +ij_yaml_indent_sequence_value = true +ij_yaml_keep_indents_on_empty_lines = false +ij_yaml_keep_line_breaks = true +ij_yaml_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/.vscode/launch.json b/.vscode/launch.json index aab8f11d5..289bc8cb9 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -63,11 +63,38 @@ } }, { - "name": "Python: File", - "type": "python", - "request": "launch", - "program": "${file}", - "justMyCode": true + "name": "Python: htcli (list Users with Include)", + "type": "python", + "request": "launch", + "program": "./ci/apiv2/htcli.py", + "args": ["list", "Users", "-v", "DEBUG", "--include", "globalPermissionGroup"], + "justMyCode": true + }, + { + "name": "Python: htcli run delete-test-data", + "type": "python", + "request": "launch", + "program": "./ci/apiv2/htcli.py", + "args": ["run", "delete-test-data", "--commit"], + "justMyCode": true + }, + { + "name": "Python: Debug pytest file", + "type": "python", + "request": "launch", + "module": "pytest", + "args": ["${file}", "--exitfirst"], + "justMyCode": true, + "env": { + "_PYTEST_RAISE": "1" + }, + }, + { + "name": "Python: File", + "type": "python", + "request": "launch", + "program": "${file}", + "justMyCode": true } ], "inputs": [ diff --git a/ci/apiv2/HACKING.md b/ci/apiv2/HACKING.md index 079b3e1a5..83cd9f97d 100644 --- a/ci/apiv2/HACKING.md +++ b/ci/apiv2/HACKING.md @@ -9,7 +9,7 @@ TOKEN=$(curl -X POST --user admin:hashtopolis http://localhost:8080/api/v2/auth/ Fetch object: ``` -curl --header "Content-Type: application/json" -X GET --header "Authorization: Bearer $TOKEN" 'http://localhost:8080/api/v2/ui/hashlists/1?expand=hashes' -d '{}' +curl --compressed --header "Authorization: Bearer $TOKEN" -g 'http://localhost:8080/api/v2/ui/hashtypes?page[size]=5' ``` Access database: @@ -23,6 +23,12 @@ docker exec $(docker ps -aqf "ancestor=mysql:8.0") mysql -u root -phashtopolis - docker exec $(docker ps -aqf "ancestor=mysql:8.0") tail -f /tmp/mysql_all.log ``` +Shortcut for testing within development setup: +``` +cd ~/src/hashtopolis/server/ci/apiv2 +pytest --exitfirst --last-failed +``` + ### paper flipchart scribbles #### v2 beta diff --git a/ci/apiv2/conftest.py b/ci/apiv2/conftest.py new file mode 100644 index 000000000..bd563400d --- /dev/null +++ b/ci/apiv2/conftest.py @@ -0,0 +1,12 @@ +import os +import pytest + +if os.getenv('_PYTEST_RAISE', "0") != "0": + + @pytest.hookimpl(tryfirst=True) + def pytest_exception_interact(call): + raise call.excinfo.value + + @pytest.hookimpl(tryfirst=True) + def pytest_internalerror(excinfo): + raise excinfo.value \ No newline at end of file diff --git a/ci/apiv2/create_crackertype_001.json b/ci/apiv2/create_crackertype_001.json index fc72c8144..9082b7f34 100644 --- a/ci/apiv2/create_crackertype_001.json +++ b/ci/apiv2/create_crackertype_001.json @@ -1,4 +1,4 @@ { - "typeName": "generic", + "typeName": "generic2", "isChunkingAvailable": true } diff --git a/ci/apiv2/dummy.yaml b/ci/apiv2/dummy.yaml new file mode 100644 index 000000000..6c44265e8 --- /dev/null +++ b/ci/apiv2/dummy.yaml @@ -0,0 +1,14 @@ +components: + schemas: + Hash: + type: object + properties: + id: + type: integer + minimum: 1 + readOnly: true + userMembers: + type: array + items: + $ref: #/components/schemas/User + uniqueItems: true diff --git a/ci/apiv2/hashtopolis.py b/ci/apiv2/hashtopolis.py index 600fa7abb..27723bdf3 100644 --- a/ci/apiv2/hashtopolis.py +++ b/ci/apiv2/hashtopolis.py @@ -6,10 +6,11 @@ # import copy import json -import requests -from pathlib import Path - import logging +from pathlib import Path +import requests +import sys +import urllib import http import confidence @@ -55,6 +56,25 @@ class HashtopolisResponseError(HashtopolisError): pass +class IncludedCache(object): + """ + Cast (potentially) included objects to object structure which + allows for caching and easier retrival + """ + def __init__(self, included_objects): + self._cache = {} + for included_obj in included_objects: + self._cache[self.get_object_uuid(included_obj)] = included_obj + + @staticmethod + def get_object_uuid(obj): + """ Generate unique key identifier for object """ + return "%s.%i" % (obj['type'], obj['id']) + + def get(self, obj): + return self._cache[self.get_object_uuid(obj)] + + class HashtopolisConnector(object): # Cache authorisation token per endpoint token = {} @@ -63,7 +83,7 @@ class HashtopolisConnector(object): @staticmethod def resp_to_json(response): content_type_header = response.headers.get('Content-Type', '') - if 'application/json' in content_type_header: + if any([x in content_type_header for x in ('application/vnd.api+json', 'application/json')]): return response.json() else: raise HashtopolisResponseError("Response type '%s' is not valid JSON document, text='%s'" % @@ -94,9 +114,17 @@ def authenticate(self): self._token_expires = HashtopolisConnector.token_expires[self._api_endpoint] self._headers = { - 'Authorization': 'Bearer ' + self._token, - 'Content-Type': 'application/json' + 'Authorization': 'Bearer ' + self._token } + + def create_payload(self, obj, attributes, id=None): + payload = {"data": { + "type": type(obj).__name__, + "attributes": attributes + }} + if id is not None: + payload["data"]["id"] = id + return payload def validate_status_code(self, r, expected_status_code, error_msg): """ Validate response and convert to python exception """ @@ -110,44 +138,99 @@ def validate_status_code(self, r, expected_status_code, error_msg): # Application hits a problem if r.status_code not in expected_status_code: - raise HashtopolisError( + raise HashtopolisResponseError( "%s (status_code=%s): %s" % (error_msg, r.status_code, r.text), status_code=r.status_code, exception_details=r_json.get('exception', []), message=r_json.get('message', None)) + + def validate_pagination_links(self, response, page): + """Validate all the links that are used for paginated data""" + data = response["data"] + highest_id = max(data, key=lambda obj: obj['id'])['id'] + lowest_id = min(data, key=lambda obj: obj['id'])['id'] + + links = response["links"] + query_params = urllib.parse.parse_qs(urllib.parse.urlparse(links["next"]).query) + assert (int(query_params["page[size]"][0]) == page["size"]) + assert (int(query_params["page[after]"][0]) == highest_id) + query_params = urllib.parse.parse_qs(urllib.parse.urlparse(links["prev"]).query) + assert (int(query_params["page[size]"][0]) == page["size"]) + assert (int(query_params["page[before]"][0]) == lowest_id) + query_params = urllib.parse.parse_qs(urllib.parse.urlparse(links["first"]).query) + assert (int(query_params["page[size]"][0]) == page["size"]) + assert (int(query_params["page[after]"][0]) == 0) + # query_params = urllib.parse.parse_qs(urllib.parse.urlparse(links["last"]).query) + # TODO not really a straightforward way to validate the last link + + def get_single_page(self, page, filter): + """Gets a single page by using the page parameters""" + self.authenticate() + headers = self._headers + request_uri = self._api_endpoint + self._model_uri + payload = {} - def filter(self, expand, max_results, ordering, filter): + for k, v in page.items(): + payload[f"page[{k}]"] = v + if filter: + for k, v in filter.items(): + payload[f"filter[{k}]"] = v + + request_uri = self._api_endpoint + self._model_uri + '?' + urllib.parse.urlencode(payload) + r = requests.get(request_uri, headers=headers) + logger.debug("Request URI: %s", urllib.parse.unquote(r.url)) + self.validate_status_code(r, [200], "paging failed") + response = self.resp_to_json(r) + logger.debug("Response %s", json.dumps(response, indent=4)) + + # validate page links + self.validate_pagination_links(response, page) + return response["data"] + + # todo refactor start_offset into page variable + def filter(self, include, ordering, filter, start_offset): self.authenticate() - uri = self._api_endpoint + self._model_uri headers = self._headers - filter_list = [f'{k}={v}' for k, v in filter.items()] - payload = { - 'filter': filter_list, - 'maxResults': max_results if max_results is not None else 999, - } - if expand is not None: - payload['expand'] = expand - if ordering is not None: - if type(ordering) is not list: - payload['ordering'] = [ordering] - else: - payload['ordering'] = ordering + payload = {'page[after]': start_offset} + if filter: + for k, v in filter.items(): + payload[f"filter[{k}]"] = v + + if include: + payload['include'] = ','.join(include) if type(include) in (list, tuple) else include + if ordering: + payload['sort'] = ','.join(ordering) if type(ordering) in (list, tuple) else ordering + + request_uri = self._api_endpoint + self._model_uri + '?' + urllib.parse.urlencode(payload) + while True: + r = requests.get(request_uri, headers=headers) + logger.debug("Request URI: %s", urllib.parse.unquote(r.url)) + self.validate_status_code(r, [200], "Filtering failed") + response = self.resp_to_json(r) + logger.debug("Response %s", json.dumps(response, indent=4)) + + # Buffer all included objects + included_cache = IncludedCache(response.get('included', [])) - r = requests.get(uri, headers=headers, data=json.dumps(payload)) - self.validate_status_code(r, [200], "Filtering failed") - return self.resp_to_json(r).get('values') + # Iterate over response objects + for obj in response['data']: + yield (obj, included_cache) - def get_one(self, pk, expand): + if 'links' not in response or 'next' not in response['links'] or not response['links']['next']: + break + request_uri = self._hashtopolis_uri + response['links']['next'] + + def get_one(self, pk, include): self.authenticate() uri = self._api_endpoint + self._model_uri + f'/{pk}' headers = self._headers payload = {} - if expand is not None: - payload['expand'] = expand + if include is not None: + payload['include'] = ','.join(include) if type(include) in (list, tuple) else include - r = requests.get(uri, headers=headers, data=json.dumps(payload)) + r = requests.get(uri, headers=headers, data=payload) self.validate_status_code(r, [200], "Get single object failed") return self.resp_to_json(r) @@ -157,43 +240,49 @@ def patch_one(self, obj): return self.authenticate() - uri = self._hashtopolis_uri + obj._self + uri = self._hashtopolis_uri + obj.uri headers = self._headers - payload = {} + headers['Content-Type'] = 'application/json' + attributes = {} for k, v in obj.diff().items(): logger.debug("Going to patch object '%s' property '%s' from '%s' to '%s'", obj, k, v[0], v[1]) - payload[k] = v[1] - + attributes[k] = v[1] + + payload = self.create_payload(obj, attributes, id=obj.id) logger.debug("Sending PATCH payload: %s to %s", json.dumps(payload), uri) r = requests.patch(uri, headers=headers, data=json.dumps(payload)) self.validate_status_code(r, [201], "Patching failed") # TODO: Validate if return objects matches digital twin - obj.set_initial(self.resp_to_json(r).copy()) + obj.set_initial(self.resp_to_json(r)['data'].copy()) def create(self, obj): # Check if object to be created is new - assert not hasattr(obj, '_self') + assert obj._new_model is True self.authenticate() uri = self._api_endpoint + self._model_uri headers = self._headers - payload = obj.get_fields() + headers['Content-Type'] = 'application/json' + + attributes = obj.get_fields() + payload = self.create_payload(obj, attributes) + logger.debug("Sending POST payload: %s to %s", json.dumps(payload), uri) r = requests.post(uri, headers=headers, data=json.dumps(payload)) self.validate_status_code(r, [201], "Creation of object failed") # TODO: Validate if return objects matches digital twin - obj.set_initial(self.resp_to_json(r).copy()) + obj.set_initial(self.resp_to_json(r)['data'].copy()) def delete(self, obj): """ Delete object from database """ # TODO: Check if object to be deleted actually exists - assert hasattr(obj, '_self') + assert obj._new_model is False self.authenticate() - uri = self._hashtopolis_uri + obj._self + uri = self._hashtopolis_uri + obj.uri headers = self._headers payload = {} @@ -203,11 +292,109 @@ def delete(self, obj): # TODO: Cleanup object to allow re-creation +# Build Django ORM style django.query interface +class QuerySet(): + def __init__(self, cls, include=None, ordering=None, filters=None, pages=None): + self.cls = cls + self.include = include + self.ordering = ordering + self.filters = filters + self.pages = pages + + def __iter__(self): + yield from self.__getitem__(slice(None, None, 1)) + + def __getitem__(self, k): + if isinstance(k, int): + return list(self.filter_(k, k + 1, 1))[0] + + if isinstance(k, slice): + return self.filter_(k.start or 0, k.stop or sys.maxsize, k.step or 1) + + def get_pagination(self): + objs = self.cls.get_conn().get_single_page(self.pages, self.filters) + parsed_objs = [] + for obj in objs: + parsed_objs.append(self.cls._model(**obj)) + return parsed_objs + + def filter_(self, start, stop, step): + index = start or 0 + cursor = index + + # pk field is special and should be translated + if self.filters is None: + filters = None + else: + filters = self.filters.copy() + if 'pk' in filters: + filters['_id'] = filters['pk'] + del filters['pk'] + + filter_generator = self.cls.get_conn().filter(self.include, self.ordering, filters, start_offset=cursor) + + while index < stop: + # Fetch new entries in chunks default to server + try: + (obj, included_cache) = next(filter_generator) + except StopIteration: + return + + # Return value + model_obj = self.cls._model(**obj) + model_obj.set_prefetched_relationships(included_cache) + yield model_obj + + index += 1 + + # Remove items skipped by step + for _ in range(step - 1): + try: + _ = next(filter_generator) + except StopIteration: + return + + def order_by(self, *ordering): + self.ordering = ordering + return self + + def filter(self, **filters): + self.filters = filters + return self + + def page(self, **pages): + self.pages = pages + return self + + def all(self): + # yield from self + return self + + def get(self, **filters): + if filters: + self.filters = filters + + # Generiek retrival, only need two entries to find out failures + objs = list(self.__getitem__(slice(0, 2, 1))) + if len(objs) == 0: + raise self.cls._model.DoesNotExist + elif len(objs) > 1: + raise self.cls._model.MultipleObjectsReturned + return objs[0] + + def __len__(self): + return len(list(iter(self))) + + class ManagerBase(type): conn = {} # Cache configuration values config = None + @classmethod + def prefetch_related(cls, *include): + return QuerySet(cls, include=include) + @classmethod def get_conn(cls): if cls.config is None: @@ -218,12 +405,11 @@ def get_conn(cls): return cls.conn[cls._model_uri] @classmethod - def all(cls, expand=None, max_results=None, ordering=None): + def all(cls): """ Retrieve all backend objects - TODO: Make iterator supporting loading of objects via pages """ - return cls.filter(expand, max_results, ordering) + return cls.filter() @classmethod def patch(cls, obj): @@ -242,43 +428,20 @@ def get_first(cls): """ Retrieve first object TODO: Error handling if first object does not exists - TODO: Request object with limit parameter instead """ return cls.all()[0] @classmethod - def get(cls, expand=None, ordering=None, **kwargs): - if 'pk' in kwargs: - try: - api_obj = cls.get_conn().get_one(kwargs['pk'], expand) - except HashtopolisError as e: - if e.status_code == 404: - raise cls._model.DoesNotExist - else: - # Re-raise error if generic failure took place - raise - new_obj = cls._model(**api_obj) - return new_obj - else: - objs = cls.filter(expand, ordering, **kwargs) - if len(objs) == 0: - raise cls._model.DoesNotExist - elif len(objs) > 1: - raise cls._model.MultipleObjectsReturned - return objs[0] + def get(cls, **filters): + return QuerySet(cls, filters=filters).get() @classmethod - def filter(cls, expand=None, max_results=None, ordering=None, **kwargs): - # Get all objects - api_objs = cls.get_conn().filter(expand, max_results, ordering, kwargs) + def paginate(cls, **pages): + return QuerySet(cls, pages=pages) - # Convert into class - objs = [] - if api_objs: - for api_obj in api_objs: - new_obj = cls._model(**api_obj) - objs.append(new_obj) - return objs + @classmethod + def filter(cls, **filters): + return QuerySet(cls, filters=filters) class ObjectDoesNotExist(Exception): @@ -306,7 +469,7 @@ def add_to_class(class_name, class_type): class_name, type(class_name, (class_type,), { "__qualname__": "%s.%s" % (new_class.__qualname__, class_name), - '__module__': "%s.%s" % (__name__, new_class.__name__) + '__module__': "%s" % (__name__) })) add_to_class('DoesNotExist', ObjectDoesNotExist) add_to_class('MultipleObjectsReturned', MultipleObjectsReturned) @@ -331,78 +494,91 @@ def add_to_class(class_name, class_type): class Model(metaclass=ModelBase): def __init__(self, *args, **kwargs): - self.set_initial(kwargs) + if 'links' in kwargs: + # Loading of existing model + self.set_initial(kwargs) + else: + self.set_initial({'attributes': kwargs}) super().__init__() def __repr__(self): - return self._self + return self.__uri def __eq__(self, other): return (self.get_fields() == other.get_fields()) def _dict2obj(self, dict): - # Function to convert a dict to an object. - uri = dict.get('_self') + """ + Convert resource object dictionary to an model Object + """ + uri = dict['links']['self'] + uri_without_id = '/'.join(uri.split('/')[:-1]) # Loop through all the registers classes for _, model in cls_registry.items(): model_uri = model.objects._model_uri # Check if part of the uri is in the model uri - if model_uri in uri: + if uri_without_id.endswith(model_uri): return model(**dict) # If we are here, it means that no uri matched, thus we don't know the object. - raise TypeError('Object not valid model') + raise TypeError(f"Object identifier '{uri}' not valid/defined model") def set_initial(self, kv): self.__fields = [] - self.__expanded = [] + self.__included = [] + self._new_model = True # Store fields allowing us to detect changed values - if '_self' in kv: + if 'links' in kv: self.__initial = copy.deepcopy(kv) + self.__uri = kv['links']['self'] + self.__id = kv['id'] + self._new_model = False else: # New model self.__initial = {} + self.__relationships = kv.get('relationships', {}) + # Create attribute values - for k, v in kv.items(): - # In case expand is true, there can be a attribute which also is an object. - # Example: Users in AccessGroups. This part will convert the returned data. - # Into proper objects. - if type(v) is list and len(v) > 0: - # Many-to-Many relation - obj_list = [] - # Loop through all the values in the list and convert them to objects. - for dict_v in v: - if type(dict_v) is dict and dict_v.get('_self'): - # Double check that it really is an object - obj = self._dict2obj(dict_v) - obj_list.append(obj) - # Set the attribute of the current object to a set object (like Django) - # Also check if it really were objects - if len(obj_list) > 0: - setattr(self, f"{k}_set", obj_list) - self.__expanded.append(f"{k}_set") - continue - # This does the same as above, only one-to-one relations - if type(v) is dict and v.get('_self'): - setattr(self, f"{k}", self._dict2obj(v)) - self.__expanded.append(f"{k}") - continue + for k, v in kv['attributes'].items(): + setattr(self, k, v) + self.__fields.append(k) - # Skip over field 'id', as it is automatic property of model itself. - # This should be removed if there is a concensus on the full model. - # Example: not rightgroupName but name, and not rightgroupId but id - if k != 'id': - setattr(self, k, v) + def set_prefetched_relationships(self, included_cache): + """ + Populate prefetched relationships + """ + for relationship_name, resource_identifier_object in self.__relationships.items(): + if 'data' not in resource_identifier_object: + # TODO Deal with 'link' type related relationships + continue - if not k.startswith('_'): - self.__fields.append(k) + resource_identifier_object_data_type = type(resource_identifier_object['data']) + if resource_identifier_object_data_type is type(None): + # Empty to-one relationship + setattr(self, relationship_name, None) + self.__included.append(relationship_name) + elif resource_identifier_object_data_type is dict: + # Non-empty to-one relationship + to_one_relation_obj = self._dict2obj(included_cache.get(resource_identifier_object['data'])) + setattr(self, relationship_name, to_one_relation_obj) + self.__included.append(relationship_name) + elif resource_identifier_object_data_type is list: + to_many_relation_objs = [] + # to-many relationship + for obj in resource_identifier_object['data']: + to_many_relation_objs.append(self._dict2obj(included_cache.get(obj))) + setattr(self, relationship_name + '_set', to_many_relation_objs) + self.__included.append(relationship_name + "_set") + else: + raise AssertionError("Invalid resource indentifier object class type=%s" % + resource_identifier_object_data_type) def get_fields(self): return dict([(k, getattr(self, k)) for k in sorted(self.__fields)]) def diff(self): # Stored database values - d_initial = self.__initial + d_initial = self.__initial['attributes'] # Possible changes values d_current = self.get_fields() diffs = [] @@ -411,15 +587,15 @@ def diff(self): if v_current != v_innitial: diffs.append((key, (v_innitial, v_current))) - # Find expandables sets which have changed - for expand in self.__expanded: - if expand.endswith('_set'): - innitial_name = expand[:-4] + # Find includeables sets which have changed + for include in self.__included: + if include.endswith('_set'): + innitial_name = include[:-4] # Retrieve innitial keys - v_innitial = self.__initial[innitial_name] - v_innitial_ids = [x['_id'] for x in v_innitial] + v_innitial = self.__initial['relationships'][innitial_name]['data'] + v_innitial_ids = [x['id'] for x in v_innitial] # Retrieve new/current keys - v_current = getattr(self, expand) + v_current = getattr(self, include) v_current_ids = [x.id for x in v_current] # Use ID of ojbects as new current/update identifiers if sorted(v_innitial_ids) != sorted(v_current_ids): @@ -431,28 +607,36 @@ def has_changed(self): return bool(self.diff()) def save(self): - if hasattr(self, '_self'): - self.objects.patch(self) - else: + if self._new_model: self.objects.create(self) + else: + self.objects.patch(self) return self def delete(self): - if hasattr(self, '_self'): + if not self._new_model: self.objects.delete(self) def serialize(self): - retval = dict([(x, getattr(self, x)) for x in self.__fields] + [('_self', self._self), ('_id', self._id)]) - for expandable in self.__expanded: - if expandable.endswith('_set'): - retval[expandable] = [x.serialize() for x in getattr(self, expandable)] + retval = dict([(x, getattr(self, x)) for x in self.__fields] + [('_self', self.__uri), ('_id', self.__id)]) + for includeable in self.__included: + if includeable.endswith('_set'): + retval[includeable] = [x.serialize() for x in getattr(self, includeable)] else: - retval[expandable] = getattr(self, expandable).serialize() + retval[includeable] = getattr(self, includeable).serialize() return retval @property def id(self): - return self._id + return self.__id + + @property + def pk(self): + return self.__id + + @property + def uri(self): + return self.__uri ## @@ -585,7 +769,6 @@ def do_upload(self, filename, file_stream, chunk_size=1000000000): uri = self._api_endpoint + self._model_uri my_client = tusclient.client.TusClient(uri) - del self._headers['Content-Type'] my_client.set_headers(self._headers) metadata = {"filename": filename, @@ -625,6 +808,7 @@ def _helper_request(self, helper_uri, payload): self.authenticate() uri = self._api_endpoint + self._model_uri + helper_uri headers = self._headers + headers['Content-Type'] = 'application/json' logging.debug(f"Makeing POST request to {uri}, headers={headers} payload={payload}") r = requests.post(uri, headers=headers, data=json.dumps(payload)) diff --git a/ci/apiv2/htcli.py b/ci/apiv2/htcli.py index 4b9575caa..e71e5264c 100755 --- a/ci/apiv2/htcli.py +++ b/ci/apiv2/htcli.py @@ -21,7 +21,9 @@ from utils import find_stale_test_objects logger = logging.getLogger(__name__) -click_log.basic_config(logger) + +root_logger = logging.getLogger() +click_log.basic_config(root_logger) ALL_MODELS = [x[1] for x in inspect.getmembers(hashtopolis, inspect.isclass) if issubclass(x[1], hashtopolis.Model) and x[1] is not hashtopolis.Model] @@ -39,7 +41,7 @@ def run(): @run.command() @click.option('-c', '--commit', is_flag=True, help="Non-interactive mode") -@click_log.simple_verbosity_option(logger) +@click_log.simple_verbosity_option(root_logger) def delete_test_data(commit): if commit is False: prefix = '[DRY-RUN]' @@ -59,13 +61,13 @@ def delete_test_data(commit): @main.command() @click.argument('model_plural', type=click.Choice([x.verbose_name_plural for x in ALL_MODELS], case_sensitive=True)) @click.option('-b', '--brief', 'is_brief', is_flag=True, help="Condense output to list of items") -@click.option('--expand', 'opt_expand', help="Comma seperated list of items to expand", multiple=True) +@click.option('--include', 'opt_include', help="Comma seperated list of relations to include", multiple=True) @click.option('--fields', 'opt_fields', help="Comma seperated list of fields to display", multiple=True) @click.option('--filter', 'opt_filter', help="Filter objects based on filter provided", multiple=True) @click.option('--ordering', 'opt_ordering', help="Field to select for ordering output", multiple=True) @click.option('--max_results', 'opt_max_results', default=None, help="Maximum results to display", type=int) -@click_log.simple_verbosity_option(logger) -def list(model_plural, is_brief, opt_expand, opt_fields, opt_filter, opt_max_results, opt_ordering): +@click_log.simple_verbosity_option(root_logger) +def list(model_plural, is_brief, opt_include, opt_fields, opt_filter, opt_max_results, opt_ordering): model_class = [x for x in ALL_MODELS if x.verbose_name_plural == model_plural][0] def get_opt_list(options): @@ -76,7 +78,7 @@ def get_opt_list(options): return () # Parse options and arguments - expand = get_opt_list(opt_expand) + include = get_opt_list(opt_include) filter = dict([filter_item.split('=', 1) for filter_item in get_opt_list(opt_filter) if filter_item]) display_field_filter = get_opt_list(opt_fields) @@ -85,9 +87,9 @@ def get_opt_list(options): # Retrieve objects if not opt_filter: - objs = model_class.objects.all(expand, max_results=opt_max_results) + objs = model_class.objects.prefetch_related(*include).all()[:opt_max_results] else: - objs = model_class.objects.filter(expand, max_results=opt_max_results, **filter) + objs = model_class.objects.prefetch_related(*include).filter(**filter)[:opt_max_results] # Display objects if is_brief is True: @@ -113,5 +115,4 @@ def get_opt_list(options): if __name__ == '__main__': - logging.basicConfig() main() diff --git a/ci/apiv2/test_agent.py b/ci/apiv2/test_agent.py index 1aae2c315..a4e833c2e 100644 --- a/ci/apiv2/test_agent.py +++ b/ci/apiv2/test_agent.py @@ -25,9 +25,18 @@ def test_patch_field_ignorerrors_invalid_choice(self): self._test_patch(model_obj, 'ignoreErrors', 5) self.assertEqual(e.exception.status_code, 500) + def test_name_too_long(self): + model_obj = self.create_test_object() + too_long_name = "a" * 101 + with self.assertRaises(HashtopolisError) as e: + self._test_patch(model_obj, 'agentName', too_long_name) # name exceeds max size of 100 + self.assertEqual(e.exception.status_code, 500) + self.assertEqual(e.exception.exception_details[0]["message"], + f"The string value: '{too_long_name}' is too long. The max size is '100'") + def test_expandables(self): model_obj = self.create_test_object() - expandables = ['accessGroups', 'agentstats'] + expandables = ['accessGroups', 'agentStats'] self._test_expandables(model_obj, expandables) def test_assign_unassign_agent(self): diff --git a/ci/apiv2/test_agentassignment.py b/ci/apiv2/test_agentassignment.py index ee8a44106..fd94f1ca8 100644 --- a/ci/apiv2/test_agentassignment.py +++ b/ci/apiv2/test_agentassignment.py @@ -17,7 +17,7 @@ def test_patch(self): model_obj = self.create_test_object() with self.assertRaises(HashtopolisResponseError) as e: self._test_patch(model_obj, 'agentId', 1234) - self.assertEqual(e.exception.status_code, 500) + self.assertEqual(e.exception.status_code, 405) def test_delete(self): model_obj = self.create_test_object(delete=False) diff --git a/ci/apiv2/test_attributes.py b/ci/apiv2/test_attributes.py index 2ade1600c..278c7dda1 100644 --- a/ci/apiv2/test_attributes.py +++ b/ci/apiv2/test_attributes.py @@ -24,13 +24,15 @@ def test_patch_read_only(self): conn.authenticate() headers = conn._headers + headers['Content-Type'] = 'application/json' uri = conn._api_endpoint + conn._model_uri + f'/{user.id}' - payload = {} - payload['passwordHash'] = 'test' + attributes = {} + attributes['passwordHash'] = 'test' + payload = conn.create_payload(user, attributes, id=user.id) r = requests.patch(uri, headers=headers, data=json.dumps(payload)) - self.assertEqual(r.status_code, 500) + self.assertEqual(r.status_code, 403) self.assertIn('immutable', r.json().get('exception')[0].get('message')) user.delete() @@ -46,6 +48,7 @@ def test_create_protected(self): ) with self.assertRaises(HashtopolisError) as e: user.save() + self.assertEqual(e.exception.status_code, 500) self.assertIn(' not valid input ', e.exception.exception_details[0]['message']) diff --git a/ci/apiv2/test_expand.py b/ci/apiv2/test_expand.py index 080397d8b..fcbbdfa27 100644 --- a/ci/apiv2/test_expand.py +++ b/ci/apiv2/test_expand.py @@ -5,7 +5,7 @@ class ExpandTest(BaseTest): def test_accessgroups_usermembers_m2m(self): # Many-to-many casting - objs = AccessGroup.objects.all(expand='userMembers') + objs = AccessGroup.objects.prefetch_related('userMembers').all() # Check the default account self.assertEqual(objs[0].userMembers_set[0].name, 'admin') @@ -14,11 +14,11 @@ def test_crackerbinary_o2o(self): hashlist = self.create_hashlist() task = self.create_task(hashlist) - objs = Task.objects.filter(taskId=task.id, expand='crackerBinary') + objs = Task.objects.prefetch_related('crackerBinary').filter(taskId=task.id) self.assertEqual(objs[0].crackerBinary.binaryName, 'hashcat') def test_individual_object_expanding(self): hashlist = self.create_hashlist() - obj = Hashlist.objects.get(pk=hashlist.id, expand='hashes') + obj = Hashlist.objects.prefetch_related('hashes').get(pk=hashlist.id) self.assertEqual('cc03e747a6afbbcbf8be7668acfebee5', obj.hashes_set[0].hash) diff --git a/ci/apiv2/test_filter_and_ordering.py b/ci/apiv2/test_filter_and_ordering.py index 5597e35a5..a43819d66 100644 --- a/ci/apiv2/test_filter_and_ordering.py +++ b/ci/apiv2/test_filter_and_ordering.py @@ -1,6 +1,5 @@ from hashtopolis import HashType from utils import BaseTest -import pytest class FilterTest(BaseTest): @@ -44,21 +43,21 @@ def test_filter__eq(self): objs = HashType.objects.filter(hashTypeId__eq=100) all_objs = HashType.objects.all() self.assertEqual( - [x.id for x in all_objs if x.hashTypeId == 100], + [x.id for x in all_objs if x.id == 100], [x.id for x in objs]) def test_filter__gt(self): objs = HashType.objects.filter(hashTypeId__gt=8000) all_objs = HashType.objects.all() self.assertEqual( - [x.id for x in all_objs if x.hashTypeId > 8000], + [x.id for x in all_objs if x.id > 8000], [x.id for x in objs]) def test_filter__gte(self): objs = HashType.objects.filter(hashTypeId__gte=8000) all_objs = HashType.objects.all() self.assertEqual( - [x.id for x in all_objs if x.hashTypeId >= 8000], + [x.id for x in all_objs if x.id >= 8000], [x.id for x in objs]) def test_filter__icontains(self): @@ -89,23 +88,24 @@ def test_filter__lt(self): objs = HashType.objects.filter(hashTypeId__lt=100) all_objs = HashType.objects.all() self.assertEqual( - [x.id for x in all_objs if x.hashTypeId < 100], + [x.id for x in all_objs if x.id < 100], [x.id for x in objs]) def test_filter__lte(self): objs = HashType.objects.filter(hashTypeId__lte=100) all_objs = HashType.objects.all() self.assertEqual( - [x.id for x in all_objs if x.hashTypeId <= 100], + [x.id for x in all_objs if x.id <= 100], [x.id for x in objs]) def test_filter__ne(self): objs = HashType.objects.filter(hashTypeId__ne=100) all_objs = HashType.objects.all() self.assertEqual( - [x.id for x in all_objs if x.hashTypeId != 100], + [x.id for x in all_objs if x.id != 100], [x.id for x in objs]) + # is this test correct? No description starts with net so just an empty array gets compared to an empty array def test_filter__startswith(self): objs = HashType.objects.filter(description__startswith="net") all_objs = HashType.objects.all() @@ -115,18 +115,19 @@ def test_filter__startswith(self): def test_ordering(self): model_objs = self.create_test_objects() - objs = HashType.objects.filter(hashTypeId__gte=90000, hashTypeId__lte=91000, - ordering=['-hashTypeId']) - sorted_model_objs = sorted(model_objs, key=lambda x: x.hashTypeId, reverse=True) + objs = HashType.objects.filter(hashTypeId__gte=90000, hashTypeId__lte=91000).order_by('-hashTypeId') + sorted_model_objs = sorted(model_objs, key=lambda x: x.id, reverse=True) self.assertEqual( [x.id for x in sorted_model_objs], [x.id for x in objs]) def test_ordering_twice(self): model_objs = self.create_test_objects() - objs = HashType.objects.filter(hashTypeId__gte=90000, hashTypeId__lte=91000, - ordering=['-isSalted', '-hashTypeId']) - sorted_model_objs = sorted(model_objs, key=lambda x: (x.isSalted, x.hashTypeId), reverse=True) + objs = ( + HashType.objects.filter(hashTypeId__gte=90000, hashTypeId__lte=91000) + .order_by('-isSalted', '-hashTypeId') + ) + sorted_model_objs = sorted(model_objs, key=lambda x: (x.isSalted, x.id), reverse=True) self.assertEqual( [x.id for x in sorted_model_objs], [x.id for x in objs]) diff --git a/ci/apiv2/test_globalpermissiongroup.py b/ci/apiv2/test_globalpermissiongroup.py index de3b20233..ebe6e4741 100644 --- a/ci/apiv2/test_globalpermissiongroup.py +++ b/ci/apiv2/test_globalpermissiongroup.py @@ -29,5 +29,5 @@ def test_delete(self): def test_expand(self): model_obj = self.create_test_object() - expandables = ['user'] + expandables = ['userMembers'] self._test_expandables(model_obj, expandables) diff --git a/ci/apiv2/test_hash.py b/ci/apiv2/test_hash.py index 34f4c7e72..4eabf03da 100644 --- a/ci/apiv2/test_hash.py +++ b/ci/apiv2/test_hash.py @@ -1,4 +1,4 @@ -from hashtopolis import Hash, HashtopolisResponseError +from hashtopolis import Hash, HashtopolisResponseError, HashtopolisError from utils import BaseTest @@ -20,14 +20,14 @@ def test_patch(self): model_obj = self.create_test_object() with self.assertRaises(HashtopolisResponseError) as e: self._test_patch(model_obj, 'isCracked', True) - self.assertEqual(e.exception.status_code, 500) + self.assertEqual(e.exception.status_code, 405) def test_delete(self): # Deleting Hashes is not possible via API model_obj = self.create_test_object() with self.assertRaises(HashtopolisResponseError) as e: self._test_delete(model_obj) - self.assertEqual(e.exception.status_code, 500) + self.assertEqual(e.exception.status_code, 405) def test_expandables(self): model_obj = self.create_test_object() diff --git a/ci/apiv2/test_hashlist.py b/ci/apiv2/test_hashlist.py index 5ddd36d1a..d48567ffa 100644 --- a/ci/apiv2/test_hashlist.py +++ b/ci/apiv2/test_hashlist.py @@ -147,5 +147,5 @@ def test_helper_create_superhashlist(self): self.assertEqual(hashlist.format, 3) # Validate if created with provided hashlists - obj = Hashlist.objects.get(pk=hashlist.id, expand='hashlists') + obj = Hashlist.objects.prefetch_related('hashlists').get(pk=hashlist.id) self.assertListEqual(hashlists, obj.hashlists_set) diff --git a/ci/apiv2/test_http_methods.py b/ci/apiv2/test_http_methods.py index 7bdc790d9..d70e2d47b 100644 --- a/ci/apiv2/test_http_methods.py +++ b/ci/apiv2/test_http_methods.py @@ -11,11 +11,12 @@ def test_empty_body(self): conn.authenticate() headers = conn._headers - del headers['Content-Type'] - uri = conn._api_endpoint + conn._model_uri r = requests.get(uri, headers=headers) - values = r.json().get('values') + values = r.json().get('jsonapi') self.assertGreaterEqual(len(values), 1) + + # TODO: Test for non-empty body which should fail + # TODO: Test for invalid parameters \ No newline at end of file diff --git a/ci/apiv2/test_pagination.py b/ci/apiv2/test_pagination.py new file mode 100644 index 000000000..deb8af57a --- /dev/null +++ b/ci/apiv2/test_pagination.py @@ -0,0 +1,25 @@ +from hashtopolis import HashType +from utils import BaseTest + + +class PaginationTest(BaseTest): + model_class = HashType + + def pagination_test_helper(self, after, size): + objs = HashType.objects.paginate(size=size, after=after).get_pagination() + all_objs = list(HashType.objects.all()) + index = None + for idx, obj in enumerate(all_objs): + if obj.id > after: + index = idx + break + + self.assertIsNotNone(index) + self.assertEqual(objs, all_objs[index:index+size]) + pass + + def test_get_page(self): + # TODO test can be randomised to get more coverage + self.pagination_test_helper(1200, 25) + self.pagination_test_helper(2500, 50) + self.pagination_test_helper(20, 10) diff --git a/ci/apiv2/test_speed.py b/ci/apiv2/test_speed.py index 78b8e549d..2026e809f 100644 --- a/ci/apiv2/test_speed.py +++ b/ci/apiv2/test_speed.py @@ -18,14 +18,14 @@ def test_patch(self): model_obj = self.create_test_object() with self.assertRaises(HashtopolisResponseError) as e: self._test_patch(model_obj, 'speed', 1234) - self.assertEqual(e.exception.status_code, 500) + self.assertEqual(e.exception.status_code, 405) def test_delete(self): # Delete should not be possible via API model_obj = self.create_test_object() with self.assertRaises(HashtopolisResponseError) as e: self._test_delete(model_obj) - self.assertEqual(e.exception.status_code, 500) + self.assertEqual(e.exception.status_code, 405) def test_expandables(self): model_obj = self.create_test_object() diff --git a/ci/apiv2/test_supertask.py b/ci/apiv2/test_supertask.py index 1ea89eb97..2214e2a53 100644 --- a/ci/apiv2/test_supertask.py +++ b/ci/apiv2/test_supertask.py @@ -30,11 +30,11 @@ def test_new_pretasks(self): model_obj = self.create_test_object() # Quirk for expanding object to allow update to take place - work_obj = Supertask.objects.get(pk=model_obj.id, expand='pretasks') + work_obj = Supertask.objects.prefetch_related('pretasks').get(pk=model_obj.id) new_pretasks = [self.create_pretask() for i in range(2)] selected_pretasks = [work_obj.pretasks_set[0], new_pretasks[1]] work_obj.pretasks_set = selected_pretasks work_obj.save() - obj = Supertask.objects.get(pk=model_obj.id, expand='pretasks') + obj = Supertask.objects.prefetch_related('pretasks').get(pk=model_obj.id) self.assertListEqual(selected_pretasks, obj.pretasks_set) diff --git a/ci/apiv2/test_task.py b/ci/apiv2/test_task.py index ab3b9fb96..b58a4d7e5 100644 --- a/ci/apiv2/test_task.py +++ b/ci/apiv2/test_task.py @@ -73,7 +73,7 @@ def test_task_with_file(self): # Not part of default model fields, how-ever expanded field extra_payload = dict(files=[x.id for x in files]) task = self.create_task(hashlist, extra_payload=extra_payload) - obj = Task.objects.get(pk=task.id, expand='files') + obj = Task.objects.prefetch_related('files').get(pk=task.id) self.assertListEqual([x.id for x in files], [x.id for x in obj.files_set]) def test_task_update_priority(self): diff --git a/ci/apiv2/test_taskwrapper.py b/ci/apiv2/test_taskwrapper.py index 4eadc8d29..51421a40b 100644 --- a/ci/apiv2/test_taskwrapper.py +++ b/ci/apiv2/test_taskwrapper.py @@ -27,7 +27,7 @@ def test_patch_immutable(self): model_obj = self.create_test_object() with self.assertRaises(HashtopolisError) as e: self._test_patch(model_obj, 'taskType', 2) - self.assertEqual(e.exception.status_code, 500) + self.assertEqual(e.exception.status_code, 403) def test_delete(self): model_obj = self.create_test_object(delete=False) diff --git a/ci/apiv2/utils.py b/ci/apiv2/utils.py index 04bd33a89..53ed89601 100644 --- a/ci/apiv2/utils.py +++ b/ci/apiv2/utils.py @@ -57,7 +57,7 @@ def do_create_dummy_agent(): dummy_agent.update_information() # Validate automatically deleted when an test-agent claims the voucher - assert Voucher.objects.filter(_id=voucher.id) == [] + assert list(Voucher.objects.filter(_id=voucher.id)) == [] agent = Agent.objects.get(agentName=dummy_agent.name) return (dummy_agent, agent) @@ -369,7 +369,7 @@ def _test_delete(self, model_obj): def _test_expandables(self, model_obj, expandables): """ Generic test worker to test expandables""" # Retrieve object expanded and check if exists - obj = self.model_class.objects.get(pk=model_obj.id, expand=expandables) + obj = self.model_class.objects.prefetch_related(*expandables).get(pk=model_obj.id) self.assertIsNotNone(obj) for expandable in expandables: self.assertTrue(hasattr(obj, expandable) or hasattr(obj, f"{expandable}_set"), diff --git a/composer.json b/composer.json index 10277d800..31dbf3031 100644 --- a/composer.json +++ b/composer.json @@ -19,15 +19,16 @@ "require": { "php": "^7.4 || ^8.0", "ext-json": "*", + "ext-pdo": "*", "crell/api-problem": "^3.6", + "middlewares/encoder": "^2.1", "middlewares/negotiation": "^2.1", "monolog/monolog": "^2.8", "php-di/php-di": "^6.4", "slim/psr7": "^1.5", "slim/slim": "^4.10", "tuupola/slim-basic-auth": "^3.3", - "tuupola/slim-jwt-auth": "^3.6", - "ext-pdo" : "*" + "tuupola/slim-jwt-auth": "^3.6" }, "require-dev": { "jangregor/phpstan-prophecy": "^1.0.0", diff --git a/composer.lock b/composer.lock new file mode 100644 index 000000000..eb6982d8c --- /dev/null +++ b/composer.lock @@ -0,0 +1,4167 @@ +{ + "_readme": [ + "This file locks the dependencies of your project to a known state", + "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", + "This file is @generated automatically" + ], + "content-hash": "2a07cc9c28bff6ab34aa2b4db3e30a69", + "packages": [ + { + "name": "crell/api-problem", + "version": "3.6.1", + "source": { + "type": "git", + "url": "https://github.com/Crell/ApiProblem.git", + "reference": "5acb0a8cc13ea740f631a60e5e73271c18e45803" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Crell/ApiProblem/zipball/5acb0a8cc13ea740f631a60e5e73271c18e45803", + "reference": "5acb0a8cc13ea740f631a60e5e73271c18e45803", + "shasum": "" + }, + "require": { + "php": "^7.1 || ^8.0" + }, + "require-dev": { + "laminas/laminas-diactoros": "^2.0", + "phpstan/phpstan": "^1.3", + "phpunit/phpunit": "^7.0 || ^8.0 || ^9.0", + "psr/http-factory": "^1.0", + "psr/http-message": "1.*" + }, + "suggest": { + "psr/http-factory": "Common interfaces for PSR-7 HTTP message factories", + "psr/http-message": "Common interface for HTTP messages" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Crell\\ApiProblem\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Larry Garfield", + "email": "larry@garfieldtech.com", + "homepage": "http://www.garfieldtech.com/" + } + ], + "description": "PHP wrapper for the api-problem IETF specification", + "homepage": "https://github.com/Crell/ApiProblem", + "keywords": [ + "api-problem", + "http", + "json", + "rest", + "xml" + ], + "support": { + "issues": "https://github.com/Crell/ApiProblem/issues", + "source": "https://github.com/Crell/ApiProblem/tree/3.6.1" + }, + "funding": [ + { + "url": "https://github.com/Crell", + "type": "github" + } + ], + "time": "2022-01-04T15:47:30+00:00" + }, + { + "name": "fig/http-message-util", + "version": "1.1.5", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-message-util.git", + "reference": "9d94dc0154230ac39e5bf89398b324a86f63f765" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-message-util/zipball/9d94dc0154230ac39e5bf89398b324a86f63f765", + "reference": "9d94dc0154230ac39e5bf89398b324a86f63f765", + "shasum": "" + }, + "require": { + "php": "^5.3 || ^7.0 || ^8.0" + }, + "suggest": { + "psr/http-message": "The package containing the PSR-7 interfaces" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.1.x-dev" + } + }, + "autoload": { + "psr-4": { + "Fig\\Http\\Message\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Utility classes and constants for use with PSR-7 (psr/http-message)", + "keywords": [ + "http", + "http-message", + "psr", + "psr-7", + "request", + "response" + ], + "support": { + "issues": "https://github.com/php-fig/http-message-util/issues", + "source": "https://github.com/php-fig/http-message-util/tree/1.1.5" + }, + "time": "2020-11-24T22:02:12+00:00" + }, + { + "name": "firebase/php-jwt", + "version": "v5.5.1", + "source": { + "type": "git", + "url": "https://github.com/firebase/php-jwt.git", + "reference": "83b609028194aa042ea33b5af2d41a7427de80e6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/firebase/php-jwt/zipball/83b609028194aa042ea33b5af2d41a7427de80e6", + "reference": "83b609028194aa042ea33b5af2d41a7427de80e6", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "require-dev": { + "phpunit/phpunit": ">=4.8 <=9" + }, + "suggest": { + "paragonie/sodium_compat": "Support EdDSA (Ed25519) signatures when libsodium is not present" + }, + "type": "library", + "autoload": { + "psr-4": { + "Firebase\\JWT\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Neuman Vong", + "email": "neuman+pear@twilio.com", + "role": "Developer" + }, + { + "name": "Anant Narayanan", + "email": "anant@php.net", + "role": "Developer" + } + ], + "description": "A simple library to encode and decode JSON Web Tokens (JWT) in PHP. Should conform to the current spec.", + "homepage": "https://github.com/firebase/php-jwt", + "keywords": [ + "jwt", + "php" + ], + "support": { + "issues": "https://github.com/firebase/php-jwt/issues", + "source": "https://github.com/firebase/php-jwt/tree/v5.5.1" + }, + "time": "2021-11-08T20:18:51+00:00" + }, + { + "name": "laravel/serializable-closure", + "version": "v1.3.4", + "source": { + "type": "git", + "url": "https://github.com/laravel/serializable-closure.git", + "reference": "61b87392d986dc49ad5ef64e75b1ff5fee24ef81" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/laravel/serializable-closure/zipball/61b87392d986dc49ad5ef64e75b1ff5fee24ef81", + "reference": "61b87392d986dc49ad5ef64e75b1ff5fee24ef81", + "shasum": "" + }, + "require": { + "php": "^7.3|^8.0" + }, + "require-dev": { + "illuminate/support": "^8.0|^9.0|^10.0|^11.0", + "nesbot/carbon": "^2.61|^3.0", + "pestphp/pest": "^1.21.3", + "phpstan/phpstan": "^1.8.2", + "symfony/var-dumper": "^5.4.11|^6.2.0|^7.0.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.x-dev" + } + }, + "autoload": { + "psr-4": { + "Laravel\\SerializableClosure\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Taylor Otwell", + "email": "taylor@laravel.com" + }, + { + "name": "Nuno Maduro", + "email": "nuno@laravel.com" + } + ], + "description": "Laravel Serializable Closure provides an easy and secure way to serialize closures in PHP.", + "keywords": [ + "closure", + "laravel", + "serializable" + ], + "support": { + "issues": "https://github.com/laravel/serializable-closure/issues", + "source": "https://github.com/laravel/serializable-closure" + }, + "time": "2024-08-02T07:48:17+00:00" + }, + { + "name": "middlewares/encoder", + "version": "v2.1.1", + "source": { + "type": "git", + "url": "https://github.com/middlewares/encoder.git", + "reference": "6fd1744bcf88bec4e3dea0ca98ed6f900cc41ce0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/middlewares/encoder/zipball/6fd1744bcf88bec4e3dea0ca98ed6f900cc41ce0", + "reference": "6fd1744bcf88bec4e3dea0ca98ed6f900cc41ce0", + "shasum": "" + }, + "require": { + "ext-zlib": "*", + "middlewares/utils": "^3.0", + "php": "^7.2 || ^8.0", + "psr/http-server-middleware": "^1.0" + }, + "require-dev": { + "friendsofphp/php-cs-fixer": "^2.0", + "laminas/laminas-diactoros": "^2.2", + "oscarotero/php-cs-fixer-config": "^1.0", + "phpstan/phpstan": "^0.12", + "phpunit/phpunit": "^8|^9", + "squizlabs/php_codesniffer": "^3.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Middlewares\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Middleware to encode the response body to gzip or deflate", + "homepage": "https://github.com/middlewares/encoder", + "keywords": [ + "compression", + "deflate", + "encoding", + "gzip", + "http", + "middleware", + "psr-15", + "psr-7" + ], + "support": { + "issues": "https://github.com/middlewares/encoder/issues", + "source": "https://github.com/middlewares/encoder/tree/v2.1.1" + }, + "time": "2020-12-03T01:13:28+00:00" + }, + { + "name": "middlewares/negotiation", + "version": "v2.1.1", + "source": { + "type": "git", + "url": "https://github.com/middlewares/negotiation.git", + "reference": "d2d44ea744109216ef9569653c179b2005c77a85" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/middlewares/negotiation/zipball/d2d44ea744109216ef9569653c179b2005c77a85", + "reference": "d2d44ea744109216ef9569653c179b2005c77a85", + "shasum": "" + }, + "require": { + "middlewares/utils": "^3.0 || ^4.0", + "php": "^7.2 || ^8.0", + "psr/http-server-middleware": "^1.0", + "willdurand/negotiation": "^3.0" + }, + "require-dev": { + "friendsofphp/php-cs-fixer": "^2.0", + "laminas/laminas-diactoros": "^2.2", + "oscarotero/php-cs-fixer-config": "^1.0", + "phpstan/phpstan": "^0.12", + "phpunit/phpunit": "^8|^9|^10|^11", + "squizlabs/php_codesniffer": "^3.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Middlewares\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Middleware to implement content negotiation", + "homepage": "https://github.com/middlewares/negotiation", + "keywords": [ + "content", + "encoding", + "http", + "language", + "middleware", + "negotiation", + "psr-15", + "psr-7", + "server" + ], + "support": { + "issues": "https://github.com/middlewares/negotiation/issues", + "source": "https://github.com/middlewares/negotiation/tree/v2.1.1" + }, + "time": "2024-03-24T14:24:30+00:00" + }, + { + "name": "middlewares/utils", + "version": "v3.3.0", + "source": { + "type": "git", + "url": "https://github.com/middlewares/utils.git", + "reference": "670b135ce0dbd040eadb025a9388f9bd617cc010" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/middlewares/utils/zipball/670b135ce0dbd040eadb025a9388f9bd617cc010", + "reference": "670b135ce0dbd040eadb025a9388f9bd617cc010", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0", + "psr/container": "^1.0 || ^2.0", + "psr/http-factory": "^1.0", + "psr/http-message": "^1.0", + "psr/http-server-middleware": "^1.0" + }, + "require-dev": { + "friendsofphp/php-cs-fixer": "^v2.16", + "guzzlehttp/psr7": "^2.0", + "laminas/laminas-diactoros": "^2.4", + "nyholm/psr7": "^1.0", + "oscarotero/php-cs-fixer-config": "^1.0", + "phpstan/phpstan": "^0.12", + "phpunit/phpunit": "^8|^9", + "slim/psr7": "^1.4", + "squizlabs/php_codesniffer": "^3.5", + "sunrise/http-message": "^1.0", + "sunrise/http-server-request": "^1.0", + "sunrise/stream": "^1.0.15", + "sunrise/uri": "^1.0.15" + }, + "type": "library", + "autoload": { + "psr-4": { + "Middlewares\\Utils\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Common utils for PSR-15 middleware packages", + "homepage": "https://github.com/middlewares/utils", + "keywords": [ + "PSR-11", + "http", + "middleware", + "psr-15", + "psr-17", + "psr-7" + ], + "support": { + "issues": "https://github.com/middlewares/utils/issues", + "source": "https://github.com/middlewares/utils/tree/v3.3.0" + }, + "time": "2021-07-04T17:56:23+00:00" + }, + { + "name": "monolog/monolog", + "version": "2.9.3", + "source": { + "type": "git", + "url": "https://github.com/Seldaek/monolog.git", + "reference": "a30bfe2e142720dfa990d0a7e573997f5d884215" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Seldaek/monolog/zipball/a30bfe2e142720dfa990d0a7e573997f5d884215", + "reference": "a30bfe2e142720dfa990d0a7e573997f5d884215", + "shasum": "" + }, + "require": { + "php": ">=7.2", + "psr/log": "^1.0.1 || ^2.0 || ^3.0" + }, + "provide": { + "psr/log-implementation": "1.0.0 || 2.0.0 || 3.0.0" + }, + "require-dev": { + "aws/aws-sdk-php": "^2.4.9 || ^3.0", + "doctrine/couchdb": "~1.0@dev", + "elasticsearch/elasticsearch": "^7 || ^8", + "ext-json": "*", + "graylog2/gelf-php": "^1.4.2 || ^2@dev", + "guzzlehttp/guzzle": "^7.4", + "guzzlehttp/psr7": "^2.2", + "mongodb/mongodb": "^1.8", + "php-amqplib/php-amqplib": "~2.4 || ^3", + "phpspec/prophecy": "^1.15", + "phpstan/phpstan": "^1.10", + "phpunit/phpunit": "^8.5.38 || ^9.6.19", + "predis/predis": "^1.1 || ^2.0", + "rollbar/rollbar": "^1.3 || ^2 || ^3", + "ruflin/elastica": "^7", + "swiftmailer/swiftmailer": "^5.3|^6.0", + "symfony/mailer": "^5.4 || ^6", + "symfony/mime": "^5.4 || ^6" + }, + "suggest": { + "aws/aws-sdk-php": "Allow sending log messages to AWS services like DynamoDB", + "doctrine/couchdb": "Allow sending log messages to a CouchDB server", + "elasticsearch/elasticsearch": "Allow sending log messages to an Elasticsearch server via official client", + "ext-amqp": "Allow sending log messages to an AMQP server (1.0+ required)", + "ext-curl": "Required to send log messages using the IFTTTHandler, the LogglyHandler, the SendGridHandler, the SlackWebhookHandler or the TelegramBotHandler", + "ext-mbstring": "Allow to work properly with unicode symbols", + "ext-mongodb": "Allow sending log messages to a MongoDB server (via driver)", + "ext-openssl": "Required to send log messages using SSL", + "ext-sockets": "Allow sending log messages to a Syslog server (via UDP driver)", + "graylog2/gelf-php": "Allow sending log messages to a GrayLog2 server", + "mongodb/mongodb": "Allow sending log messages to a MongoDB server (via library)", + "php-amqplib/php-amqplib": "Allow sending log messages to an AMQP server using php-amqplib", + "rollbar/rollbar": "Allow sending log messages to Rollbar", + "ruflin/elastica": "Allow sending log messages to an Elastic Search server" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "2.x-dev" + } + }, + "autoload": { + "psr-4": { + "Monolog\\": "src/Monolog" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be", + "homepage": "https://seld.be" + } + ], + "description": "Sends your logs to files, sockets, inboxes, databases and various web services", + "homepage": "https://github.com/Seldaek/monolog", + "keywords": [ + "log", + "logging", + "psr-3" + ], + "support": { + "issues": "https://github.com/Seldaek/monolog/issues", + "source": "https://github.com/Seldaek/monolog/tree/2.9.3" + }, + "funding": [ + { + "url": "https://github.com/Seldaek", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/monolog/monolog", + "type": "tidelift" + } + ], + "time": "2024-04-12T20:52:51+00:00" + }, + { + "name": "nikic/fast-route", + "version": "v1.3.0", + "source": { + "type": "git", + "url": "https://github.com/nikic/FastRoute.git", + "reference": "181d480e08d9476e61381e04a71b34dc0432e812" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/nikic/FastRoute/zipball/181d480e08d9476e61381e04a71b34dc0432e812", + "reference": "181d480e08d9476e61381e04a71b34dc0432e812", + "shasum": "" + }, + "require": { + "php": ">=5.4.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.8.35|~5.7" + }, + "type": "library", + "autoload": { + "files": [ + "src/functions.php" + ], + "psr-4": { + "FastRoute\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Nikita Popov", + "email": "nikic@php.net" + } + ], + "description": "Fast request router for PHP", + "keywords": [ + "router", + "routing" + ], + "support": { + "issues": "https://github.com/nikic/FastRoute/issues", + "source": "https://github.com/nikic/FastRoute/tree/master" + }, + "time": "2018-02-13T20:26:39+00:00" + }, + { + "name": "php-di/invoker", + "version": "2.3.4", + "source": { + "type": "git", + "url": "https://github.com/PHP-DI/Invoker.git", + "reference": "33234b32dafa8eb69202f950a1fc92055ed76a86" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/PHP-DI/Invoker/zipball/33234b32dafa8eb69202f950a1fc92055ed76a86", + "reference": "33234b32dafa8eb69202f950a1fc92055ed76a86", + "shasum": "" + }, + "require": { + "php": ">=7.3", + "psr/container": "^1.0|^2.0" + }, + "require-dev": { + "athletic/athletic": "~0.1.8", + "mnapoli/hard-mode": "~0.3.0", + "phpunit/phpunit": "^9.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Invoker\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Generic and extensible callable invoker", + "homepage": "https://github.com/PHP-DI/Invoker", + "keywords": [ + "callable", + "dependency", + "dependency-injection", + "injection", + "invoke", + "invoker" + ], + "support": { + "issues": "https://github.com/PHP-DI/Invoker/issues", + "source": "https://github.com/PHP-DI/Invoker/tree/2.3.4" + }, + "funding": [ + { + "url": "https://github.com/mnapoli", + "type": "github" + } + ], + "time": "2023-09-08T09:24:21+00:00" + }, + { + "name": "php-di/php-di", + "version": "6.4.0", + "source": { + "type": "git", + "url": "https://github.com/PHP-DI/PHP-DI.git", + "reference": "ae0f1b3b03d8b29dff81747063cbfd6276246cc4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/PHP-DI/PHP-DI/zipball/ae0f1b3b03d8b29dff81747063cbfd6276246cc4", + "reference": "ae0f1b3b03d8b29dff81747063cbfd6276246cc4", + "shasum": "" + }, + "require": { + "laravel/serializable-closure": "^1.0", + "php": ">=7.4.0", + "php-di/invoker": "^2.0", + "php-di/phpdoc-reader": "^2.0.1", + "psr/container": "^1.0" + }, + "provide": { + "psr/container-implementation": "^1.0" + }, + "require-dev": { + "doctrine/annotations": "~1.10", + "friendsofphp/php-cs-fixer": "^2.4", + "mnapoli/phpunit-easymock": "^1.2", + "ocramius/proxy-manager": "^2.11.2", + "phpstan/phpstan": "^0.12", + "phpunit/phpunit": "^9.5" + }, + "suggest": { + "doctrine/annotations": "Install it if you want to use annotations (version ~1.2)", + "ocramius/proxy-manager": "Install it if you want to use lazy injection (version ~2.0)" + }, + "type": "library", + "autoload": { + "files": [ + "src/functions.php" + ], + "psr-4": { + "DI\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "The dependency injection container for humans", + "homepage": "https://php-di.org/", + "keywords": [ + "PSR-11", + "container", + "container-interop", + "dependency injection", + "di", + "ioc", + "psr11" + ], + "support": { + "issues": "https://github.com/PHP-DI/PHP-DI/issues", + "source": "https://github.com/PHP-DI/PHP-DI/tree/6.4.0" + }, + "funding": [ + { + "url": "https://github.com/mnapoli", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/php-di/php-di", + "type": "tidelift" + } + ], + "time": "2022-04-09T16:46:38+00:00" + }, + { + "name": "php-di/phpdoc-reader", + "version": "2.2.1", + "source": { + "type": "git", + "url": "https://github.com/PHP-DI/PhpDocReader.git", + "reference": "66daff34cbd2627740ffec9469ffbac9f8c8185c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/PHP-DI/PhpDocReader/zipball/66daff34cbd2627740ffec9469ffbac9f8c8185c", + "reference": "66daff34cbd2627740ffec9469ffbac9f8c8185c", + "shasum": "" + }, + "require": { + "php": ">=7.2.0" + }, + "require-dev": { + "mnapoli/hard-mode": "~0.3.0", + "phpunit/phpunit": "^8.5|^9.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "PhpDocReader\\": "src/PhpDocReader" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "PhpDocReader parses @var and @param values in PHP docblocks (supports namespaced class names with the same resolution rules as PHP)", + "keywords": [ + "phpdoc", + "reflection" + ], + "support": { + "issues": "https://github.com/PHP-DI/PhpDocReader/issues", + "source": "https://github.com/PHP-DI/PhpDocReader/tree/2.2.1" + }, + "time": "2020-10-12T12:39:22+00:00" + }, + { + "name": "psr/container", + "version": "1.1.2", + "source": { + "type": "git", + "url": "https://github.com/php-fig/container.git", + "reference": "513e0666f7216c7459170d56df27dfcefe1689ea" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/container/zipball/513e0666f7216c7459170d56df27dfcefe1689ea", + "reference": "513e0666f7216c7459170d56df27dfcefe1689ea", + "shasum": "" + }, + "require": { + "php": ">=7.4.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Psr\\Container\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common Container Interface (PHP FIG PSR-11)", + "homepage": "https://github.com/php-fig/container", + "keywords": [ + "PSR-11", + "container", + "container-interface", + "container-interop", + "psr" + ], + "support": { + "issues": "https://github.com/php-fig/container/issues", + "source": "https://github.com/php-fig/container/tree/1.1.2" + }, + "time": "2021-11-05T16:50:12+00:00" + }, + { + "name": "psr/http-factory", + "version": "1.1.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-factory.git", + "reference": "2b4765fddfe3b508ac62f829e852b1501d3f6e8a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-factory/zipball/2b4765fddfe3b508ac62f829e852b1501d3f6e8a", + "reference": "2b4765fddfe3b508ac62f829e852b1501d3f6e8a", + "shasum": "" + }, + "require": { + "php": ">=7.1", + "psr/http-message": "^1.0 || ^2.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Message\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "PSR-17: Common interfaces for PSR-7 HTTP message factories", + "keywords": [ + "factory", + "http", + "message", + "psr", + "psr-17", + "psr-7", + "request", + "response" + ], + "support": { + "source": "https://github.com/php-fig/http-factory" + }, + "time": "2024-04-15T12:06:14+00:00" + }, + { + "name": "psr/http-message", + "version": "1.1", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-message.git", + "reference": "cb6ce4845ce34a8ad9e68117c10ee90a29919eba" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-message/zipball/cb6ce4845ce34a8ad9e68117c10ee90a29919eba", + "reference": "cb6ce4845ce34a8ad9e68117c10ee90a29919eba", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.1.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Message\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interface for HTTP messages", + "homepage": "https://github.com/php-fig/http-message", + "keywords": [ + "http", + "http-message", + "psr", + "psr-7", + "request", + "response" + ], + "support": { + "source": "https://github.com/php-fig/http-message/tree/1.1" + }, + "time": "2023-04-04T09:50:52+00:00" + }, + { + "name": "psr/http-server-handler", + "version": "1.0.2", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-server-handler.git", + "reference": "84c4fb66179be4caaf8e97bd239203245302e7d4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-server-handler/zipball/84c4fb66179be4caaf8e97bd239203245302e7d4", + "reference": "84c4fb66179be4caaf8e97bd239203245302e7d4", + "shasum": "" + }, + "require": { + "php": ">=7.0", + "psr/http-message": "^1.0 || ^2.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Server\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interface for HTTP server-side request handler", + "keywords": [ + "handler", + "http", + "http-interop", + "psr", + "psr-15", + "psr-7", + "request", + "response", + "server" + ], + "support": { + "source": "https://github.com/php-fig/http-server-handler/tree/1.0.2" + }, + "time": "2023-04-10T20:06:20+00:00" + }, + { + "name": "psr/http-server-middleware", + "version": "1.0.2", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-server-middleware.git", + "reference": "c1481f747daaa6a0782775cd6a8c26a1bf4a3829" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-server-middleware/zipball/c1481f747daaa6a0782775cd6a8c26a1bf4a3829", + "reference": "c1481f747daaa6a0782775cd6a8c26a1bf4a3829", + "shasum": "" + }, + "require": { + "php": ">=7.0", + "psr/http-message": "^1.0 || ^2.0", + "psr/http-server-handler": "^1.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Server\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interface for HTTP server-side middleware", + "keywords": [ + "http", + "http-interop", + "middleware", + "psr", + "psr-15", + "psr-7", + "request", + "response" + ], + "support": { + "issues": "https://github.com/php-fig/http-server-middleware/issues", + "source": "https://github.com/php-fig/http-server-middleware/tree/1.0.2" + }, + "time": "2023-04-11T06:14:47+00:00" + }, + { + "name": "psr/log", + "version": "3.0.2", + "source": { + "type": "git", + "url": "https://github.com/php-fig/log.git", + "reference": "f16e1d5863e37f8d8c2a01719f5b34baa2b714d3" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/log/zipball/f16e1d5863e37f8d8c2a01719f5b34baa2b714d3", + "reference": "f16e1d5863e37f8d8c2a01719f5b34baa2b714d3", + "shasum": "" + }, + "require": { + "php": ">=8.0.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Log\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interface for logging libraries", + "homepage": "https://github.com/php-fig/log", + "keywords": [ + "log", + "psr", + "psr-3" + ], + "support": { + "source": "https://github.com/php-fig/log/tree/3.0.2" + }, + "time": "2024-09-11T13:17:53+00:00" + }, + { + "name": "ralouphie/getallheaders", + "version": "3.0.3", + "source": { + "type": "git", + "url": "https://github.com/ralouphie/getallheaders.git", + "reference": "120b605dfeb996808c31b6477290a714d356e822" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/ralouphie/getallheaders/zipball/120b605dfeb996808c31b6477290a714d356e822", + "reference": "120b605dfeb996808c31b6477290a714d356e822", + "shasum": "" + }, + "require": { + "php": ">=5.6" + }, + "require-dev": { + "php-coveralls/php-coveralls": "^2.1", + "phpunit/phpunit": "^5 || ^6.5" + }, + "type": "library", + "autoload": { + "files": [ + "src/getallheaders.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Ralph Khattar", + "email": "ralph.khattar@gmail.com" + } + ], + "description": "A polyfill for getallheaders.", + "support": { + "issues": "https://github.com/ralouphie/getallheaders/issues", + "source": "https://github.com/ralouphie/getallheaders/tree/develop" + }, + "time": "2019-03-08T08:55:37+00:00" + }, + { + "name": "slim/psr7", + "version": "1.7.0", + "source": { + "type": "git", + "url": "https://github.com/slimphp/Slim-Psr7.git", + "reference": "753e9646def5ff4db1a06e5cf4ef539bfd30f467" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/slimphp/Slim-Psr7/zipball/753e9646def5ff4db1a06e5cf4ef539bfd30f467", + "reference": "753e9646def5ff4db1a06e5cf4ef539bfd30f467", + "shasum": "" + }, + "require": { + "fig/http-message-util": "^1.1.5", + "php": "^8.0", + "psr/http-factory": "^1.1", + "psr/http-message": "^1.0 || ^2.0", + "ralouphie/getallheaders": "^3.0", + "symfony/polyfill-php80": "^1.29" + }, + "provide": { + "psr/http-factory-implementation": "^1.0", + "psr/http-message-implementation": "^1.0 || ^2.0" + }, + "require-dev": { + "adriansuter/php-autoload-override": "^1.4", + "ext-json": "*", + "http-interop/http-factory-tests": "^1.1.0", + "php-http/psr7-integration-tests": "1.3.0", + "phpspec/prophecy": "^1.19", + "phpspec/prophecy-phpunit": "^2.2", + "phpstan/phpstan": "^1.11", + "phpunit/phpunit": "^9.6", + "squizlabs/php_codesniffer": "^3.10" + }, + "type": "library", + "autoload": { + "psr-4": { + "Slim\\Psr7\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Josh Lockhart", + "email": "hello@joshlockhart.com", + "homepage": "http://joshlockhart.com" + }, + { + "name": "Andrew Smith", + "email": "a.smith@silentworks.co.uk", + "homepage": "http://silentworks.co.uk" + }, + { + "name": "Rob Allen", + "email": "rob@akrabat.com", + "homepage": "http://akrabat.com" + }, + { + "name": "Pierre Berube", + "email": "pierre@lgse.com", + "homepage": "http://www.lgse.com" + } + ], + "description": "Strict PSR-7 implementation", + "homepage": "https://www.slimframework.com", + "keywords": [ + "http", + "psr-7", + "psr7" + ], + "support": { + "issues": "https://github.com/slimphp/Slim-Psr7/issues", + "source": "https://github.com/slimphp/Slim-Psr7/tree/1.7.0" + }, + "time": "2024-06-08T14:48:17+00:00" + }, + { + "name": "slim/slim", + "version": "4.14.0", + "source": { + "type": "git", + "url": "https://github.com/slimphp/Slim.git", + "reference": "5943393b88716eb9e82c4161caa956af63423913" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/slimphp/Slim/zipball/5943393b88716eb9e82c4161caa956af63423913", + "reference": "5943393b88716eb9e82c4161caa956af63423913", + "shasum": "" + }, + "require": { + "ext-json": "*", + "nikic/fast-route": "^1.3", + "php": "^7.4 || ^8.0", + "psr/container": "^1.0 || ^2.0", + "psr/http-factory": "^1.1", + "psr/http-message": "^1.1 || ^2.0", + "psr/http-server-handler": "^1.0", + "psr/http-server-middleware": "^1.0", + "psr/log": "^1.1 || ^2.0 || ^3.0" + }, + "require-dev": { + "adriansuter/php-autoload-override": "^1.4", + "ext-simplexml": "*", + "guzzlehttp/psr7": "^2.6", + "httpsoft/http-message": "^1.1", + "httpsoft/http-server-request": "^1.1", + "laminas/laminas-diactoros": "^2.17 || ^3", + "nyholm/psr7": "^1.8", + "nyholm/psr7-server": "^1.1", + "phpspec/prophecy": "^1.19", + "phpspec/prophecy-phpunit": "^2.1", + "phpstan/phpstan": "^1.11", + "phpunit/phpunit": "^9.6", + "slim/http": "^1.3", + "slim/psr7": "^1.6", + "squizlabs/php_codesniffer": "^3.10", + "vimeo/psalm": "^5.24" + }, + "suggest": { + "ext-simplexml": "Needed to support XML format in BodyParsingMiddleware", + "ext-xml": "Needed to support XML format in BodyParsingMiddleware", + "php-di/php-di": "PHP-DI is the recommended container library to be used with Slim", + "slim/psr7": "Slim PSR-7 implementation. See https://www.slimframework.com/docs/v4/start/installation.html for more information." + }, + "type": "library", + "autoload": { + "psr-4": { + "Slim\\": "Slim" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Josh Lockhart", + "email": "hello@joshlockhart.com", + "homepage": "https://joshlockhart.com" + }, + { + "name": "Andrew Smith", + "email": "a.smith@silentworks.co.uk", + "homepage": "http://silentworks.co.uk" + }, + { + "name": "Rob Allen", + "email": "rob@akrabat.com", + "homepage": "http://akrabat.com" + }, + { + "name": "Pierre Berube", + "email": "pierre@lgse.com", + "homepage": "http://www.lgse.com" + }, + { + "name": "Gabriel Manricks", + "email": "gmanricks@me.com", + "homepage": "http://gabrielmanricks.com" + } + ], + "description": "Slim is a PHP micro framework that helps you quickly write simple yet powerful web applications and APIs", + "homepage": "https://www.slimframework.com", + "keywords": [ + "api", + "framework", + "micro", + "router" + ], + "support": { + "docs": "https://www.slimframework.com/docs/v4/", + "forum": "https://discourse.slimframework.com/", + "irc": "irc://irc.freenode.net:6667/slimphp", + "issues": "https://github.com/slimphp/Slim/issues", + "rss": "https://www.slimframework.com/blog/feed.rss", + "slack": "https://slimphp.slack.com/", + "source": "https://github.com/slimphp/Slim", + "wiki": "https://github.com/slimphp/Slim/wiki" + }, + "funding": [ + { + "url": "https://opencollective.com/slimphp", + "type": "open_collective" + }, + { + "url": "https://tidelift.com/funding/github/packagist/slim/slim", + "type": "tidelift" + } + ], + "time": "2024-06-13T08:54:48+00:00" + }, + { + "name": "symfony/polyfill-php80", + "version": "v1.31.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php80.git", + "reference": "60328e362d4c2c802a54fcbf04f9d3fb892b4cf8" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/60328e362d4c2c802a54fcbf04f9d3fb892b4cf8", + "reference": "60328e362d4c2c802a54fcbf04f9d3fb892b4cf8", + "shasum": "" + }, + "require": { + "php": ">=7.2" + }, + "type": "library", + "extra": { + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Php80\\": "" + }, + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Ion Bazan", + "email": "ion.bazan@gmail.com" + }, + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill backporting some PHP 8.0+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-php80/tree/v1.31.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-09-09T11:45:10+00:00" + }, + { + "name": "tuupola/callable-handler", + "version": "1.1.0", + "source": { + "type": "git", + "url": "https://github.com/tuupola/callable-handler.git", + "reference": "0bc7b88630ca753de9aba8f411046856f5ca6f8c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/tuupola/callable-handler/zipball/0bc7b88630ca753de9aba8f411046856f5ca6f8c", + "reference": "0bc7b88630ca753de9aba8f411046856f5ca6f8c", + "shasum": "" + }, + "require": { + "php": "^7.1|^8.0", + "psr/http-server-middleware": "^1.0" + }, + "require-dev": { + "overtrue/phplint": "^1.0", + "phpunit/phpunit": "^7.0|^8.0|^9.0", + "squizlabs/php_codesniffer": "^3.2", + "tuupola/http-factory": "^0.4.0|^1.0", + "zendframework/zend-diactoros": "^1.6.0|^2.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Tuupola\\Middleware\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Mika Tuupola", + "email": "tuupola@appelsiini.net", + "homepage": "https://appelsiini.net/", + "role": "Developer" + } + ], + "description": "Compatibility layer for PSR-7 double pass and PSR-15 middlewares.", + "homepage": "https://github.com/tuupola/callable-handler", + "keywords": [ + "middleware", + "psr-15", + "psr-7" + ], + "support": { + "issues": "https://github.com/tuupola/callable-handler/issues", + "source": "https://github.com/tuupola/callable-handler/tree/1.1.0" + }, + "funding": [ + { + "url": "https://github.com/tuupola", + "type": "github" + } + ], + "time": "2020-09-09T08:31:54+00:00" + }, + { + "name": "tuupola/http-factory", + "version": "1.4.0", + "source": { + "type": "git", + "url": "https://github.com/tuupola/http-factory.git", + "reference": "ae3f8fbdd31cf2f1bbe920b38963c5e4d1e9c454" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/tuupola/http-factory/zipball/ae3f8fbdd31cf2f1bbe920b38963c5e4d1e9c454", + "reference": "ae3f8fbdd31cf2f1bbe920b38963c5e4d1e9c454", + "shasum": "" + }, + "require": { + "php": "^7.1|^8.0", + "psr/http-factory": "^1.0" + }, + "conflict": { + "nyholm/psr7": "<1.0" + }, + "provide": { + "psr/http-factory-implementation": "^1.0" + }, + "require-dev": { + "http-interop/http-factory-tests": "^0.9.0", + "overtrue/phplint": "^3.0", + "phpunit/phpunit": "^7.0|^8.0|^9.0", + "squizlabs/php_codesniffer": "^3.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Tuupola\\Http\\Factory\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Mika Tuupola", + "email": "tuupola@appelsiini.net", + "homepage": "https://appelsiini.net/", + "role": "Developer" + } + ], + "description": "Lightweight autodiscovering PSR-17 HTTP factories", + "homepage": "https://github.com/tuupola/http-factory", + "keywords": [ + "http", + "psr-17", + "psr-7" + ], + "support": { + "issues": "https://github.com/tuupola/http-factory/issues", + "source": "https://github.com/tuupola/http-factory/tree/1.4.0" + }, + "funding": [ + { + "url": "https://github.com/tuupola", + "type": "github" + } + ], + "time": "2021-09-14T12:46:25+00:00" + }, + { + "name": "tuupola/slim-basic-auth", + "version": "3.3.1", + "source": { + "type": "git", + "url": "https://github.com/tuupola/slim-basic-auth.git", + "reference": "18e49c18f5648b05bb6169d166ccb6f797f0fbc4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/tuupola/slim-basic-auth/zipball/18e49c18f5648b05bb6169d166ccb6f797f0fbc4", + "reference": "18e49c18f5648b05bb6169d166ccb6f797f0fbc4", + "shasum": "" + }, + "require": { + "php": "^7.1|^8.0", + "psr/http-message": "^1.0.1", + "psr/http-server-middleware": "^1.0", + "tuupola/callable-handler": "^0.3.0|^0.4.0|^1.0", + "tuupola/http-factory": "^0.4.0|^1.0.2" + }, + "require-dev": { + "equip/dispatch": "^2.0", + "overtrue/phplint": "^2.0.2", + "phpstan/phpstan": "^0.12.43", + "phpunit/phpunit": "^7.0|^8.0|^9.0", + "squizlabs/php_codesniffer": "^3.3.2", + "symfony/process": "^3.3", + "zendframework/zend-diactoros": "^1.3|^2.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Tuupola\\Middleware\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Mika Tuupola", + "email": "tuupola@appelsiini.net", + "homepage": "https://appelsiini.net/" + } + ], + "description": "PSR-7 and PSR-15 HTTP Basic Authentication Middleware", + "homepage": "https://appelsiini.net/projects/slim-basic-auth", + "keywords": [ + "auth", + "middleware", + "psr-15", + "psr-7" + ], + "support": { + "issues": "https://github.com/tuupola/slim-basic-auth/issues", + "source": "https://github.com/tuupola/slim-basic-auth/tree/3.3.1" + }, + "funding": [ + { + "url": "https://github.com/tuupola", + "type": "github" + } + ], + "time": "2020-10-28T15:22:12+00:00" + }, + { + "name": "tuupola/slim-jwt-auth", + "version": "3.8.0", + "source": { + "type": "git", + "url": "https://github.com/tuupola/slim-jwt-auth.git", + "reference": "7829d4482034e9eb5e051f3a1619db0c704ba7e7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/tuupola/slim-jwt-auth/zipball/7829d4482034e9eb5e051f3a1619db0c704ba7e7", + "reference": "7829d4482034e9eb5e051f3a1619db0c704ba7e7", + "shasum": "" + }, + "require": { + "firebase/php-jwt": "^3.0|^4.0|^5.0", + "php": "^7.4|^8.0", + "psr/http-message": "^1.0|^2.0", + "psr/http-server-middleware": "^1.0", + "psr/log": "^1.0|^2.0|^3.0", + "tuupola/callable-handler": "^1.0", + "tuupola/http-factory": "^1.3" + }, + "require-dev": { + "equip/dispatch": "^2.0", + "laminas/laminas-diactoros": "^2.0|^3.0", + "overtrue/phplint": "^1.0", + "phpstan/phpstan": "^1.10", + "phpunit/phpunit": "^7.0|^8.5.30|^9.0", + "squizlabs/php_codesniffer": "^3.7" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-3.x": "3.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Tuupola\\Middleware\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Mika Tuupola", + "email": "tuupola@appelsiini.net", + "homepage": "https://appelsiini.net/", + "role": "Developer" + } + ], + "description": "PSR-7 and PSR-15 JWT Authentication Middleware", + "homepage": "https://github.com/tuupola/slim-jwt-auth", + "keywords": [ + "auth", + "json", + "jwt", + "middleware", + "psr-15", + "psr-7" + ], + "support": { + "issues": "https://github.com/tuupola/slim-jwt-auth/issues", + "source": "https://github.com/tuupola/slim-jwt-auth/tree/3.8.0" + }, + "abandoned": "jimtools/jwt-auth", + "time": "2023-10-20T09:51:26+00:00" + }, + { + "name": "willdurand/negotiation", + "version": "3.1.0", + "source": { + "type": "git", + "url": "https://github.com/willdurand/Negotiation.git", + "reference": "68e9ea0553ef6e2ee8db5c1d98829f111e623ec2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/willdurand/Negotiation/zipball/68e9ea0553ef6e2ee8db5c1d98829f111e623ec2", + "reference": "68e9ea0553ef6e2ee8db5c1d98829f111e623ec2", + "shasum": "" + }, + "require": { + "php": ">=7.1.0" + }, + "require-dev": { + "symfony/phpunit-bridge": "^5.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.0-dev" + } + }, + "autoload": { + "psr-4": { + "Negotiation\\": "src/Negotiation" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "William Durand", + "email": "will+git@drnd.me" + } + ], + "description": "Content Negotiation tools for PHP provided as a standalone library.", + "homepage": "http://williamdurand.fr/Negotiation/", + "keywords": [ + "accept", + "content", + "format", + "header", + "negotiation" + ], + "support": { + "issues": "https://github.com/willdurand/Negotiation/issues", + "source": "https://github.com/willdurand/Negotiation/tree/3.1.0" + }, + "time": "2022-01-30T20:08:53+00:00" + } + ], + "packages-dev": [ + { + "name": "doctrine/deprecations", + "version": "1.1.3", + "source": { + "type": "git", + "url": "https://github.com/doctrine/deprecations.git", + "reference": "dfbaa3c2d2e9a9df1118213f3b8b0c597bb99fab" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/deprecations/zipball/dfbaa3c2d2e9a9df1118213f3b8b0c597bb99fab", + "reference": "dfbaa3c2d2e9a9df1118213f3b8b0c597bb99fab", + "shasum": "" + }, + "require": { + "php": "^7.1 || ^8.0" + }, + "require-dev": { + "doctrine/coding-standard": "^9", + "phpstan/phpstan": "1.4.10 || 1.10.15", + "phpstan/phpstan-phpunit": "^1.0", + "phpunit/phpunit": "^7.5 || ^8.5 || ^9.5", + "psalm/plugin-phpunit": "0.18.4", + "psr/log": "^1 || ^2 || ^3", + "vimeo/psalm": "4.30.0 || 5.12.0" + }, + "suggest": { + "psr/log": "Allows logging deprecations via PSR-3 logger implementation" + }, + "type": "library", + "autoload": { + "psr-4": { + "Doctrine\\Deprecations\\": "lib/Doctrine/Deprecations" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "A small layer on top of trigger_error(E_USER_DEPRECATED) or PSR-3 logging with options to disable all deprecations or selectively for packages.", + "homepage": "https://www.doctrine-project.org/", + "support": { + "issues": "https://github.com/doctrine/deprecations/issues", + "source": "https://github.com/doctrine/deprecations/tree/1.1.3" + }, + "time": "2024-01-30T19:34:25+00:00" + }, + { + "name": "doctrine/instantiator", + "version": "2.0.0", + "source": { + "type": "git", + "url": "https://github.com/doctrine/instantiator.git", + "reference": "c6222283fa3f4ac679f8b9ced9a4e23f163e80d0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/instantiator/zipball/c6222283fa3f4ac679f8b9ced9a4e23f163e80d0", + "reference": "c6222283fa3f4ac679f8b9ced9a4e23f163e80d0", + "shasum": "" + }, + "require": { + "php": "^8.1" + }, + "require-dev": { + "doctrine/coding-standard": "^11", + "ext-pdo": "*", + "ext-phar": "*", + "phpbench/phpbench": "^1.2", + "phpstan/phpstan": "^1.9.4", + "phpstan/phpstan-phpunit": "^1.3", + "phpunit/phpunit": "^9.5.27", + "vimeo/psalm": "^5.4" + }, + "type": "library", + "autoload": { + "psr-4": { + "Doctrine\\Instantiator\\": "src/Doctrine/Instantiator/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Marco Pivetta", + "email": "ocramius@gmail.com", + "homepage": "https://ocramius.github.io/" + } + ], + "description": "A small, lightweight utility to instantiate objects in PHP without invoking their constructors", + "homepage": "https://www.doctrine-project.org/projects/instantiator.html", + "keywords": [ + "constructor", + "instantiate" + ], + "support": { + "issues": "https://github.com/doctrine/instantiator/issues", + "source": "https://github.com/doctrine/instantiator/tree/2.0.0" + }, + "funding": [ + { + "url": "https://www.doctrine-project.org/sponsorship.html", + "type": "custom" + }, + { + "url": "https://www.patreon.com/phpdoctrine", + "type": "patreon" + }, + { + "url": "https://tidelift.com/funding/github/packagist/doctrine%2Finstantiator", + "type": "tidelift" + } + ], + "time": "2022-12-30T00:23:10+00:00" + }, + { + "name": "jangregor/phpstan-prophecy", + "version": "1.0.2", + "source": { + "type": "git", + "url": "https://github.com/Jan0707/phpstan-prophecy.git", + "reference": "5ee56c7db1d58f0578c82a35e3c1befe840e85a9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Jan0707/phpstan-prophecy/zipball/5ee56c7db1d58f0578c82a35e3c1befe840e85a9", + "reference": "5ee56c7db1d58f0578c82a35e3c1befe840e85a9", + "shasum": "" + }, + "require": { + "php": "^7.1 || ^8.0", + "phpstan/phpstan": "^1.0.0" + }, + "conflict": { + "phpspec/prophecy": "<1.7.0 || >=2.0.0", + "phpunit/phpunit": "<6.0.0 || >=12.0.0" + }, + "require-dev": { + "ergebnis/composer-normalize": "^2.1.1", + "ergebnis/license": "^1.0.0", + "ergebnis/php-cs-fixer-config": "~2.2.0", + "phpspec/prophecy": "^1.7.0", + "phpunit/phpunit": "^6.0.0 || ^7.0.0 || ^8.0.0 || ^9.0.0" + }, + "type": "phpstan-extension", + "extra": { + "phpstan": { + "includes": [ + "extension.neon" + ] + } + }, + "autoload": { + "psr-4": { + "JanGregor\\Prophecy\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jan Gregor Emge-Triebel", + "email": "jan@jangregor.me" + } + ], + "description": "Provides a phpstan/phpstan extension for phpspec/prophecy", + "support": { + "issues": "https://github.com/Jan0707/phpstan-prophecy/issues", + "source": "https://github.com/Jan0707/phpstan-prophecy/tree/1.0.2" + }, + "time": "2024-04-03T08:15:54+00:00" + }, + { + "name": "myclabs/deep-copy", + "version": "1.12.0", + "source": { + "type": "git", + "url": "https://github.com/myclabs/DeepCopy.git", + "reference": "3a6b9a42cd8f8771bd4295d13e1423fa7f3d942c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/3a6b9a42cd8f8771bd4295d13e1423fa7f3d942c", + "reference": "3a6b9a42cd8f8771bd4295d13e1423fa7f3d942c", + "shasum": "" + }, + "require": { + "php": "^7.1 || ^8.0" + }, + "conflict": { + "doctrine/collections": "<1.6.8", + "doctrine/common": "<2.13.3 || >=3 <3.2.2" + }, + "require-dev": { + "doctrine/collections": "^1.6.8", + "doctrine/common": "^2.13.3 || ^3.2.2", + "phpspec/prophecy": "^1.10", + "phpunit/phpunit": "^7.5.20 || ^8.5.23 || ^9.5.13" + }, + "type": "library", + "autoload": { + "files": [ + "src/DeepCopy/deep_copy.php" + ], + "psr-4": { + "DeepCopy\\": "src/DeepCopy/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Create deep copies (clones) of your objects", + "keywords": [ + "clone", + "copy", + "duplicate", + "object", + "object graph" + ], + "support": { + "issues": "https://github.com/myclabs/DeepCopy/issues", + "source": "https://github.com/myclabs/DeepCopy/tree/1.12.0" + }, + "funding": [ + { + "url": "https://tidelift.com/funding/github/packagist/myclabs/deep-copy", + "type": "tidelift" + } + ], + "time": "2024-06-12T14:39:25+00:00" + }, + { + "name": "nikic/php-parser", + "version": "v5.2.0", + "source": { + "type": "git", + "url": "https://github.com/nikic/PHP-Parser.git", + "reference": "23c79fbbfb725fb92af9bcf41065c8e9a0d49ddb" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/23c79fbbfb725fb92af9bcf41065c8e9a0d49ddb", + "reference": "23c79fbbfb725fb92af9bcf41065c8e9a0d49ddb", + "shasum": "" + }, + "require": { + "ext-ctype": "*", + "ext-json": "*", + "ext-tokenizer": "*", + "php": ">=7.4" + }, + "require-dev": { + "ircmaxell/php-yacc": "^0.0.7", + "phpunit/phpunit": "^9.0" + }, + "bin": [ + "bin/php-parse" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "5.0-dev" + } + }, + "autoload": { + "psr-4": { + "PhpParser\\": "lib/PhpParser" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Nikita Popov" + } + ], + "description": "A PHP parser written in PHP", + "keywords": [ + "parser", + "php" + ], + "support": { + "issues": "https://github.com/nikic/PHP-Parser/issues", + "source": "https://github.com/nikic/PHP-Parser/tree/v5.2.0" + }, + "time": "2024-09-15T16:40:33+00:00" + }, + { + "name": "phar-io/manifest", + "version": "2.0.4", + "source": { + "type": "git", + "url": "https://github.com/phar-io/manifest.git", + "reference": "54750ef60c58e43759730615a392c31c80e23176" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phar-io/manifest/zipball/54750ef60c58e43759730615a392c31c80e23176", + "reference": "54750ef60c58e43759730615a392c31c80e23176", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-libxml": "*", + "ext-phar": "*", + "ext-xmlwriter": "*", + "phar-io/version": "^3.0.1", + "php": "^7.2 || ^8.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Arne Blankerts", + "email": "arne@blankerts.de", + "role": "Developer" + }, + { + "name": "Sebastian Heuer", + "email": "sebastian@phpeople.de", + "role": "Developer" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "Developer" + } + ], + "description": "Component for reading phar.io manifest information from a PHP Archive (PHAR)", + "support": { + "issues": "https://github.com/phar-io/manifest/issues", + "source": "https://github.com/phar-io/manifest/tree/2.0.4" + }, + "funding": [ + { + "url": "https://github.com/theseer", + "type": "github" + } + ], + "time": "2024-03-03T12:33:53+00:00" + }, + { + "name": "phar-io/version", + "version": "3.2.1", + "source": { + "type": "git", + "url": "https://github.com/phar-io/version.git", + "reference": "4f7fd7836c6f332bb2933569e566a0d6c4cbed74" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phar-io/version/zipball/4f7fd7836c6f332bb2933569e566a0d6c4cbed74", + "reference": "4f7fd7836c6f332bb2933569e566a0d6c4cbed74", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0" + }, + "type": "library", + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Arne Blankerts", + "email": "arne@blankerts.de", + "role": "Developer" + }, + { + "name": "Sebastian Heuer", + "email": "sebastian@phpeople.de", + "role": "Developer" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "Developer" + } + ], + "description": "Library for handling version information and constraints", + "support": { + "issues": "https://github.com/phar-io/version/issues", + "source": "https://github.com/phar-io/version/tree/3.2.1" + }, + "time": "2022-02-21T01:04:05+00:00" + }, + { + "name": "phpdocumentor/reflection-common", + "version": "2.2.0", + "source": { + "type": "git", + "url": "https://github.com/phpDocumentor/ReflectionCommon.git", + "reference": "1d01c49d4ed62f25aa84a747ad35d5a16924662b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpDocumentor/ReflectionCommon/zipball/1d01c49d4ed62f25aa84a747ad35d5a16924662b", + "reference": "1d01c49d4ed62f25aa84a747ad35d5a16924662b", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-2.x": "2.x-dev" + } + }, + "autoload": { + "psr-4": { + "phpDocumentor\\Reflection\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jaap van Otterdijk", + "email": "opensource@ijaap.nl" + } + ], + "description": "Common reflection classes used by phpdocumentor to reflect the code structure", + "homepage": "http://www.phpdoc.org", + "keywords": [ + "FQSEN", + "phpDocumentor", + "phpdoc", + "reflection", + "static analysis" + ], + "support": { + "issues": "https://github.com/phpDocumentor/ReflectionCommon/issues", + "source": "https://github.com/phpDocumentor/ReflectionCommon/tree/2.x" + }, + "time": "2020-06-27T09:03:43+00:00" + }, + { + "name": "phpdocumentor/reflection-docblock", + "version": "5.4.1", + "source": { + "type": "git", + "url": "https://github.com/phpDocumentor/ReflectionDocBlock.git", + "reference": "9d07b3f7fdcf5efec5d1609cba3c19c5ea2bdc9c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/9d07b3f7fdcf5efec5d1609cba3c19c5ea2bdc9c", + "reference": "9d07b3f7fdcf5efec5d1609cba3c19c5ea2bdc9c", + "shasum": "" + }, + "require": { + "doctrine/deprecations": "^1.1", + "ext-filter": "*", + "php": "^7.4 || ^8.0", + "phpdocumentor/reflection-common": "^2.2", + "phpdocumentor/type-resolver": "^1.7", + "phpstan/phpdoc-parser": "^1.7", + "webmozart/assert": "^1.9.1" + }, + "require-dev": { + "mockery/mockery": "~1.3.5", + "phpstan/extension-installer": "^1.1", + "phpstan/phpstan": "^1.8", + "phpstan/phpstan-mockery": "^1.1", + "phpstan/phpstan-webmozart-assert": "^1.2", + "phpunit/phpunit": "^9.5", + "vimeo/psalm": "^5.13" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "5.x-dev" + } + }, + "autoload": { + "psr-4": { + "phpDocumentor\\Reflection\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Mike van Riel", + "email": "me@mikevanriel.com" + }, + { + "name": "Jaap van Otterdijk", + "email": "opensource@ijaap.nl" + } + ], + "description": "With this component, a library can provide support for annotations via DocBlocks or otherwise retrieve information that is embedded in a DocBlock.", + "support": { + "issues": "https://github.com/phpDocumentor/ReflectionDocBlock/issues", + "source": "https://github.com/phpDocumentor/ReflectionDocBlock/tree/5.4.1" + }, + "time": "2024-05-21T05:55:05+00:00" + }, + { + "name": "phpdocumentor/type-resolver", + "version": "1.8.2", + "source": { + "type": "git", + "url": "https://github.com/phpDocumentor/TypeResolver.git", + "reference": "153ae662783729388a584b4361f2545e4d841e3c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/153ae662783729388a584b4361f2545e4d841e3c", + "reference": "153ae662783729388a584b4361f2545e4d841e3c", + "shasum": "" + }, + "require": { + "doctrine/deprecations": "^1.0", + "php": "^7.3 || ^8.0", + "phpdocumentor/reflection-common": "^2.0", + "phpstan/phpdoc-parser": "^1.13" + }, + "require-dev": { + "ext-tokenizer": "*", + "phpbench/phpbench": "^1.2", + "phpstan/extension-installer": "^1.1", + "phpstan/phpstan": "^1.8", + "phpstan/phpstan-phpunit": "^1.1", + "phpunit/phpunit": "^9.5", + "rector/rector": "^0.13.9", + "vimeo/psalm": "^4.25" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-1.x": "1.x-dev" + } + }, + "autoload": { + "psr-4": { + "phpDocumentor\\Reflection\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Mike van Riel", + "email": "me@mikevanriel.com" + } + ], + "description": "A PSR-5 based resolver of Class names, Types and Structural Element Names", + "support": { + "issues": "https://github.com/phpDocumentor/TypeResolver/issues", + "source": "https://github.com/phpDocumentor/TypeResolver/tree/1.8.2" + }, + "time": "2024-02-23T11:10:43+00:00" + }, + { + "name": "phpspec/prophecy", + "version": "v1.19.0", + "source": { + "type": "git", + "url": "https://github.com/phpspec/prophecy.git", + "reference": "67a759e7d8746d501c41536ba40cd9c0a07d6a87" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpspec/prophecy/zipball/67a759e7d8746d501c41536ba40cd9c0a07d6a87", + "reference": "67a759e7d8746d501c41536ba40cd9c0a07d6a87", + "shasum": "" + }, + "require": { + "doctrine/instantiator": "^1.2 || ^2.0", + "php": "^7.2 || 8.0.* || 8.1.* || 8.2.* || 8.3.*", + "phpdocumentor/reflection-docblock": "^5.2", + "sebastian/comparator": "^3.0 || ^4.0 || ^5.0 || ^6.0", + "sebastian/recursion-context": "^3.0 || ^4.0 || ^5.0 || ^6.0" + }, + "require-dev": { + "phpspec/phpspec": "^6.0 || ^7.0", + "phpstan/phpstan": "^1.9", + "phpunit/phpunit": "^8.0 || ^9.0 || ^10.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.x-dev" + } + }, + "autoload": { + "psr-4": { + "Prophecy\\": "src/Prophecy" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Konstantin Kudryashov", + "email": "ever.zet@gmail.com", + "homepage": "http://everzet.com" + }, + { + "name": "Marcello Duarte", + "email": "marcello.duarte@gmail.com" + } + ], + "description": "Highly opinionated mocking framework for PHP 5.3+", + "homepage": "https://github.com/phpspec/prophecy", + "keywords": [ + "Double", + "Dummy", + "dev", + "fake", + "mock", + "spy", + "stub" + ], + "support": { + "issues": "https://github.com/phpspec/prophecy/issues", + "source": "https://github.com/phpspec/prophecy/tree/v1.19.0" + }, + "time": "2024-02-29T11:52:51+00:00" + }, + { + "name": "phpspec/prophecy-phpunit", + "version": "v2.2.0", + "source": { + "type": "git", + "url": "https://github.com/phpspec/prophecy-phpunit.git", + "reference": "16e1247e139434bce0bac09848bc5c8d882940fc" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpspec/prophecy-phpunit/zipball/16e1247e139434bce0bac09848bc5c8d882940fc", + "reference": "16e1247e139434bce0bac09848bc5c8d882940fc", + "shasum": "" + }, + "require": { + "php": "^7.3 || ^8", + "phpspec/prophecy": "^1.18", + "phpunit/phpunit": "^9.1 || ^10.1 || ^11.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.x-dev" + } + }, + "autoload": { + "psr-4": { + "Prophecy\\PhpUnit\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Christophe Coevoet", + "email": "stof@notk.org" + } + ], + "description": "Integrating the Prophecy mocking library in PHPUnit test cases", + "homepage": "http://phpspec.net", + "keywords": [ + "phpunit", + "prophecy" + ], + "support": { + "issues": "https://github.com/phpspec/prophecy-phpunit/issues", + "source": "https://github.com/phpspec/prophecy-phpunit/tree/v2.2.0" + }, + "time": "2024-03-01T08:33:58+00:00" + }, + { + "name": "phpstan/extension-installer", + "version": "1.4.3", + "source": { + "type": "git", + "url": "https://github.com/phpstan/extension-installer.git", + "reference": "85e90b3942d06b2326fba0403ec24fe912372936" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpstan/extension-installer/zipball/85e90b3942d06b2326fba0403ec24fe912372936", + "reference": "85e90b3942d06b2326fba0403ec24fe912372936", + "shasum": "" + }, + "require": { + "composer-plugin-api": "^2.0", + "php": "^7.2 || ^8.0", + "phpstan/phpstan": "^1.9.0 || ^2.0" + }, + "require-dev": { + "composer/composer": "^2.0", + "php-parallel-lint/php-parallel-lint": "^1.2.0", + "phpstan/phpstan-strict-rules": "^0.11 || ^0.12 || ^1.0" + }, + "type": "composer-plugin", + "extra": { + "class": "PHPStan\\ExtensionInstaller\\Plugin" + }, + "autoload": { + "psr-4": { + "PHPStan\\ExtensionInstaller\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Composer plugin for automatic installation of PHPStan extensions", + "keywords": [ + "dev", + "static analysis" + ], + "support": { + "issues": "https://github.com/phpstan/extension-installer/issues", + "source": "https://github.com/phpstan/extension-installer/tree/1.4.3" + }, + "time": "2024-09-04T20:21:43+00:00" + }, + { + "name": "phpstan/phpdoc-parser", + "version": "1.30.1", + "source": { + "type": "git", + "url": "https://github.com/phpstan/phpdoc-parser.git", + "reference": "51b95ec8670af41009e2b2b56873bad96682413e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/51b95ec8670af41009e2b2b56873bad96682413e", + "reference": "51b95ec8670af41009e2b2b56873bad96682413e", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0" + }, + "require-dev": { + "doctrine/annotations": "^2.0", + "nikic/php-parser": "^4.15", + "php-parallel-lint/php-parallel-lint": "^1.2", + "phpstan/extension-installer": "^1.0", + "phpstan/phpstan": "^1.5", + "phpstan/phpstan-phpunit": "^1.1", + "phpstan/phpstan-strict-rules": "^1.0", + "phpunit/phpunit": "^9.5", + "symfony/process": "^5.2" + }, + "type": "library", + "autoload": { + "psr-4": { + "PHPStan\\PhpDocParser\\": [ + "src/" + ] + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "PHPDoc parser with support for nullable, intersection and generic types", + "support": { + "issues": "https://github.com/phpstan/phpdoc-parser/issues", + "source": "https://github.com/phpstan/phpdoc-parser/tree/1.30.1" + }, + "time": "2024-09-07T20:13:05+00:00" + }, + { + "name": "phpstan/phpstan", + "version": "1.12.3", + "source": { + "type": "git", + "url": "https://github.com/phpstan/phpstan.git", + "reference": "0fcbf194ab63d8159bb70d9aa3e1350051632009" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/0fcbf194ab63d8159bb70d9aa3e1350051632009", + "reference": "0fcbf194ab63d8159bb70d9aa3e1350051632009", + "shasum": "" + }, + "require": { + "php": "^7.2|^8.0" + }, + "conflict": { + "phpstan/phpstan-shim": "*" + }, + "bin": [ + "phpstan", + "phpstan.phar" + ], + "type": "library", + "autoload": { + "files": [ + "bootstrap.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "PHPStan - PHP Static Analysis Tool", + "keywords": [ + "dev", + "static analysis" + ], + "support": { + "docs": "https://phpstan.org/user-guide/getting-started", + "forum": "https://github.com/phpstan/phpstan/discussions", + "issues": "https://github.com/phpstan/phpstan/issues", + "security": "https://github.com/phpstan/phpstan/security/policy", + "source": "https://github.com/phpstan/phpstan-src" + }, + "funding": [ + { + "url": "https://github.com/ondrejmirtes", + "type": "github" + }, + { + "url": "https://github.com/phpstan", + "type": "github" + } + ], + "time": "2024-09-09T08:10:35+00:00" + }, + { + "name": "phpunit/php-code-coverage", + "version": "9.2.32", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-code-coverage.git", + "reference": "85402a822d1ecf1db1096959413d35e1c37cf1a5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/85402a822d1ecf1db1096959413d35e1c37cf1a5", + "reference": "85402a822d1ecf1db1096959413d35e1c37cf1a5", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-libxml": "*", + "ext-xmlwriter": "*", + "nikic/php-parser": "^4.19.1 || ^5.1.0", + "php": ">=7.3", + "phpunit/php-file-iterator": "^3.0.6", + "phpunit/php-text-template": "^2.0.4", + "sebastian/code-unit-reverse-lookup": "^2.0.3", + "sebastian/complexity": "^2.0.3", + "sebastian/environment": "^5.1.5", + "sebastian/lines-of-code": "^1.0.4", + "sebastian/version": "^3.0.2", + "theseer/tokenizer": "^1.2.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.6" + }, + "suggest": { + "ext-pcov": "PHP extension that provides line coverage", + "ext-xdebug": "PHP extension that provides line coverage as well as branch and path coverage" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "9.2.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library that provides collection, processing, and rendering functionality for PHP code coverage information.", + "homepage": "https://github.com/sebastianbergmann/php-code-coverage", + "keywords": [ + "coverage", + "testing", + "xunit" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-code-coverage/issues", + "security": "https://github.com/sebastianbergmann/php-code-coverage/security/policy", + "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/9.2.32" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-08-22T04:23:01+00:00" + }, + { + "name": "phpunit/php-file-iterator", + "version": "3.0.6", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-file-iterator.git", + "reference": "cf1c2e7c203ac650e352f4cc675a7021e7d1b3cf" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/cf1c2e7c203ac650e352f4cc675a7021e7d1b3cf", + "reference": "cf1c2e7c203ac650e352f4cc675a7021e7d1b3cf", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "FilterIterator implementation that filters files based on a list of suffixes.", + "homepage": "https://github.com/sebastianbergmann/php-file-iterator/", + "keywords": [ + "filesystem", + "iterator" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-file-iterator/issues", + "source": "https://github.com/sebastianbergmann/php-file-iterator/tree/3.0.6" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2021-12-02T12:48:52+00:00" + }, + { + "name": "phpunit/php-invoker", + "version": "3.1.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-invoker.git", + "reference": "5a10147d0aaf65b58940a0b72f71c9ac0423cc67" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-invoker/zipball/5a10147d0aaf65b58940a0b72f71c9ac0423cc67", + "reference": "5a10147d0aaf65b58940a0b72f71c9ac0423cc67", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "ext-pcntl": "*", + "phpunit/phpunit": "^9.3" + }, + "suggest": { + "ext-pcntl": "*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.1-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Invoke callables with a timeout", + "homepage": "https://github.com/sebastianbergmann/php-invoker/", + "keywords": [ + "process" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-invoker/issues", + "source": "https://github.com/sebastianbergmann/php-invoker/tree/3.1.1" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-09-28T05:58:55+00:00" + }, + { + "name": "phpunit/php-text-template", + "version": "2.0.4", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-text-template.git", + "reference": "5da5f67fc95621df9ff4c4e5a84d6a8a2acf7c28" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/5da5f67fc95621df9ff4c4e5a84d6a8a2acf7c28", + "reference": "5da5f67fc95621df9ff4c4e5a84d6a8a2acf7c28", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Simple template engine.", + "homepage": "https://github.com/sebastianbergmann/php-text-template/", + "keywords": [ + "template" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-text-template/issues", + "source": "https://github.com/sebastianbergmann/php-text-template/tree/2.0.4" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-10-26T05:33:50+00:00" + }, + { + "name": "phpunit/php-timer", + "version": "5.0.3", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-timer.git", + "reference": "5a63ce20ed1b5bf577850e2c4e87f4aa902afbd2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/5a63ce20ed1b5bf577850e2c4e87f4aa902afbd2", + "reference": "5a63ce20ed1b5bf577850e2c4e87f4aa902afbd2", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "5.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Utility class for timing", + "homepage": "https://github.com/sebastianbergmann/php-timer/", + "keywords": [ + "timer" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-timer/issues", + "source": "https://github.com/sebastianbergmann/php-timer/tree/5.0.3" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-10-26T13:16:10+00:00" + }, + { + "name": "phpunit/phpunit", + "version": "9.6.20", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/phpunit.git", + "reference": "49d7820565836236411f5dc002d16dd689cde42f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/49d7820565836236411f5dc002d16dd689cde42f", + "reference": "49d7820565836236411f5dc002d16dd689cde42f", + "shasum": "" + }, + "require": { + "doctrine/instantiator": "^1.5.0 || ^2", + "ext-dom": "*", + "ext-json": "*", + "ext-libxml": "*", + "ext-mbstring": "*", + "ext-xml": "*", + "ext-xmlwriter": "*", + "myclabs/deep-copy": "^1.12.0", + "phar-io/manifest": "^2.0.4", + "phar-io/version": "^3.2.1", + "php": ">=7.3", + "phpunit/php-code-coverage": "^9.2.31", + "phpunit/php-file-iterator": "^3.0.6", + "phpunit/php-invoker": "^3.1.1", + "phpunit/php-text-template": "^2.0.4", + "phpunit/php-timer": "^5.0.3", + "sebastian/cli-parser": "^1.0.2", + "sebastian/code-unit": "^1.0.8", + "sebastian/comparator": "^4.0.8", + "sebastian/diff": "^4.0.6", + "sebastian/environment": "^5.1.5", + "sebastian/exporter": "^4.0.6", + "sebastian/global-state": "^5.0.7", + "sebastian/object-enumerator": "^4.0.4", + "sebastian/resource-operations": "^3.0.4", + "sebastian/type": "^3.2.1", + "sebastian/version": "^3.0.2" + }, + "suggest": { + "ext-soap": "To be able to generate mocks based on WSDL files", + "ext-xdebug": "PHP extension that provides line coverage as well as branch and path coverage" + }, + "bin": [ + "phpunit" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "9.6-dev" + } + }, + "autoload": { + "files": [ + "src/Framework/Assert/Functions.php" + ], + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "The PHP Unit Testing framework.", + "homepage": "https://phpunit.de/", + "keywords": [ + "phpunit", + "testing", + "xunit" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/phpunit/issues", + "security": "https://github.com/sebastianbergmann/phpunit/security/policy", + "source": "https://github.com/sebastianbergmann/phpunit/tree/9.6.20" + }, + "funding": [ + { + "url": "https://phpunit.de/sponsors.html", + "type": "custom" + }, + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/phpunit/phpunit", + "type": "tidelift" + } + ], + "time": "2024-07-10T11:45:39+00:00" + }, + { + "name": "sebastian/cli-parser", + "version": "1.0.2", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/cli-parser.git", + "reference": "2b56bea83a09de3ac06bb18b92f068e60cc6f50b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/cli-parser/zipball/2b56bea83a09de3ac06bb18b92f068e60cc6f50b", + "reference": "2b56bea83a09de3ac06bb18b92f068e60cc6f50b", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library for parsing CLI options", + "homepage": "https://github.com/sebastianbergmann/cli-parser", + "support": { + "issues": "https://github.com/sebastianbergmann/cli-parser/issues", + "source": "https://github.com/sebastianbergmann/cli-parser/tree/1.0.2" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-03-02T06:27:43+00:00" + }, + { + "name": "sebastian/code-unit", + "version": "1.0.8", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/code-unit.git", + "reference": "1fc9f64c0927627ef78ba436c9b17d967e68e120" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/code-unit/zipball/1fc9f64c0927627ef78ba436c9b17d967e68e120", + "reference": "1fc9f64c0927627ef78ba436c9b17d967e68e120", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Collection of value objects that represent the PHP code units", + "homepage": "https://github.com/sebastianbergmann/code-unit", + "support": { + "issues": "https://github.com/sebastianbergmann/code-unit/issues", + "source": "https://github.com/sebastianbergmann/code-unit/tree/1.0.8" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-10-26T13:08:54+00:00" + }, + { + "name": "sebastian/code-unit-reverse-lookup", + "version": "2.0.3", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/code-unit-reverse-lookup.git", + "reference": "ac91f01ccec49fb77bdc6fd1e548bc70f7faa3e5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/code-unit-reverse-lookup/zipball/ac91f01ccec49fb77bdc6fd1e548bc70f7faa3e5", + "reference": "ac91f01ccec49fb77bdc6fd1e548bc70f7faa3e5", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Looks up which function or method a line of code belongs to", + "homepage": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/", + "support": { + "issues": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/issues", + "source": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/tree/2.0.3" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-09-28T05:30:19+00:00" + }, + { + "name": "sebastian/comparator", + "version": "4.0.8", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/comparator.git", + "reference": "fa0f136dd2334583309d32b62544682ee972b51a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/fa0f136dd2334583309d32b62544682ee972b51a", + "reference": "fa0f136dd2334583309d32b62544682ee972b51a", + "shasum": "" + }, + "require": { + "php": ">=7.3", + "sebastian/diff": "^4.0", + "sebastian/exporter": "^4.0" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Volker Dusch", + "email": "github@wallbash.com" + }, + { + "name": "Bernhard Schussek", + "email": "bschussek@2bepublished.at" + } + ], + "description": "Provides the functionality to compare PHP values for equality", + "homepage": "https://github.com/sebastianbergmann/comparator", + "keywords": [ + "comparator", + "compare", + "equality" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/comparator/issues", + "source": "https://github.com/sebastianbergmann/comparator/tree/4.0.8" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2022-09-14T12:41:17+00:00" + }, + { + "name": "sebastian/complexity", + "version": "2.0.3", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/complexity.git", + "reference": "25f207c40d62b8b7aa32f5ab026c53561964053a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/complexity/zipball/25f207c40d62b8b7aa32f5ab026c53561964053a", + "reference": "25f207c40d62b8b7aa32f5ab026c53561964053a", + "shasum": "" + }, + "require": { + "nikic/php-parser": "^4.18 || ^5.0", + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library for calculating the complexity of PHP code units", + "homepage": "https://github.com/sebastianbergmann/complexity", + "support": { + "issues": "https://github.com/sebastianbergmann/complexity/issues", + "source": "https://github.com/sebastianbergmann/complexity/tree/2.0.3" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2023-12-22T06:19:30+00:00" + }, + { + "name": "sebastian/diff", + "version": "4.0.6", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/diff.git", + "reference": "ba01945089c3a293b01ba9badc29ad55b106b0bc" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/ba01945089c3a293b01ba9badc29ad55b106b0bc", + "reference": "ba01945089c3a293b01ba9badc29ad55b106b0bc", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3", + "symfony/process": "^4.2 || ^5" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Kore Nordmann", + "email": "mail@kore-nordmann.de" + } + ], + "description": "Diff implementation", + "homepage": "https://github.com/sebastianbergmann/diff", + "keywords": [ + "diff", + "udiff", + "unidiff", + "unified diff" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/diff/issues", + "source": "https://github.com/sebastianbergmann/diff/tree/4.0.6" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-03-02T06:30:58+00:00" + }, + { + "name": "sebastian/environment", + "version": "5.1.5", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/environment.git", + "reference": "830c43a844f1f8d5b7a1f6d6076b784454d8b7ed" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/830c43a844f1f8d5b7a1f6d6076b784454d8b7ed", + "reference": "830c43a844f1f8d5b7a1f6d6076b784454d8b7ed", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "suggest": { + "ext-posix": "*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "5.1-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Provides functionality to handle HHVM/PHP environments", + "homepage": "http://www.github.com/sebastianbergmann/environment", + "keywords": [ + "Xdebug", + "environment", + "hhvm" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/environment/issues", + "source": "https://github.com/sebastianbergmann/environment/tree/5.1.5" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2023-02-03T06:03:51+00:00" + }, + { + "name": "sebastian/exporter", + "version": "4.0.6", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/exporter.git", + "reference": "78c00df8f170e02473b682df15bfcdacc3d32d72" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/78c00df8f170e02473b682df15bfcdacc3d32d72", + "reference": "78c00df8f170e02473b682df15bfcdacc3d32d72", + "shasum": "" + }, + "require": { + "php": ">=7.3", + "sebastian/recursion-context": "^4.0" + }, + "require-dev": { + "ext-mbstring": "*", + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Volker Dusch", + "email": "github@wallbash.com" + }, + { + "name": "Adam Harvey", + "email": "aharvey@php.net" + }, + { + "name": "Bernhard Schussek", + "email": "bschussek@gmail.com" + } + ], + "description": "Provides the functionality to export PHP variables for visualization", + "homepage": "https://www.github.com/sebastianbergmann/exporter", + "keywords": [ + "export", + "exporter" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/exporter/issues", + "source": "https://github.com/sebastianbergmann/exporter/tree/4.0.6" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-03-02T06:33:00+00:00" + }, + { + "name": "sebastian/global-state", + "version": "5.0.7", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/global-state.git", + "reference": "bca7df1f32ee6fe93b4d4a9abbf69e13a4ada2c9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/bca7df1f32ee6fe93b4d4a9abbf69e13a4ada2c9", + "reference": "bca7df1f32ee6fe93b4d4a9abbf69e13a4ada2c9", + "shasum": "" + }, + "require": { + "php": ">=7.3", + "sebastian/object-reflector": "^2.0", + "sebastian/recursion-context": "^4.0" + }, + "require-dev": { + "ext-dom": "*", + "phpunit/phpunit": "^9.3" + }, + "suggest": { + "ext-uopz": "*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "5.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Snapshotting of global state", + "homepage": "http://www.github.com/sebastianbergmann/global-state", + "keywords": [ + "global state" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/global-state/issues", + "source": "https://github.com/sebastianbergmann/global-state/tree/5.0.7" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-03-02T06:35:11+00:00" + }, + { + "name": "sebastian/lines-of-code", + "version": "1.0.4", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/lines-of-code.git", + "reference": "e1e4a170560925c26d424b6a03aed157e7dcc5c5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/lines-of-code/zipball/e1e4a170560925c26d424b6a03aed157e7dcc5c5", + "reference": "e1e4a170560925c26d424b6a03aed157e7dcc5c5", + "shasum": "" + }, + "require": { + "nikic/php-parser": "^4.18 || ^5.0", + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library for counting the lines of code in PHP source code", + "homepage": "https://github.com/sebastianbergmann/lines-of-code", + "support": { + "issues": "https://github.com/sebastianbergmann/lines-of-code/issues", + "source": "https://github.com/sebastianbergmann/lines-of-code/tree/1.0.4" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2023-12-22T06:20:34+00:00" + }, + { + "name": "sebastian/object-enumerator", + "version": "4.0.4", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/object-enumerator.git", + "reference": "5c9eeac41b290a3712d88851518825ad78f45c71" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/object-enumerator/zipball/5c9eeac41b290a3712d88851518825ad78f45c71", + "reference": "5c9eeac41b290a3712d88851518825ad78f45c71", + "shasum": "" + }, + "require": { + "php": ">=7.3", + "sebastian/object-reflector": "^2.0", + "sebastian/recursion-context": "^4.0" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Traverses array structures and object graphs to enumerate all referenced objects", + "homepage": "https://github.com/sebastianbergmann/object-enumerator/", + "support": { + "issues": "https://github.com/sebastianbergmann/object-enumerator/issues", + "source": "https://github.com/sebastianbergmann/object-enumerator/tree/4.0.4" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-10-26T13:12:34+00:00" + }, + { + "name": "sebastian/object-reflector", + "version": "2.0.4", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/object-reflector.git", + "reference": "b4f479ebdbf63ac605d183ece17d8d7fe49c15c7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/object-reflector/zipball/b4f479ebdbf63ac605d183ece17d8d7fe49c15c7", + "reference": "b4f479ebdbf63ac605d183ece17d8d7fe49c15c7", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Allows reflection of object attributes, including inherited and non-public ones", + "homepage": "https://github.com/sebastianbergmann/object-reflector/", + "support": { + "issues": "https://github.com/sebastianbergmann/object-reflector/issues", + "source": "https://github.com/sebastianbergmann/object-reflector/tree/2.0.4" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-10-26T13:14:26+00:00" + }, + { + "name": "sebastian/recursion-context", + "version": "4.0.5", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/recursion-context.git", + "reference": "e75bd0f07204fec2a0af9b0f3cfe97d05f92efc1" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/e75bd0f07204fec2a0af9b0f3cfe97d05f92efc1", + "reference": "e75bd0f07204fec2a0af9b0f3cfe97d05f92efc1", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Adam Harvey", + "email": "aharvey@php.net" + } + ], + "description": "Provides functionality to recursively process PHP variables", + "homepage": "https://github.com/sebastianbergmann/recursion-context", + "support": { + "issues": "https://github.com/sebastianbergmann/recursion-context/issues", + "source": "https://github.com/sebastianbergmann/recursion-context/tree/4.0.5" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2023-02-03T06:07:39+00:00" + }, + { + "name": "sebastian/resource-operations", + "version": "3.0.4", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/resource-operations.git", + "reference": "05d5692a7993ecccd56a03e40cd7e5b09b1d404e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/resource-operations/zipball/05d5692a7993ecccd56a03e40cd7e5b09b1d404e", + "reference": "05d5692a7993ecccd56a03e40cd7e5b09b1d404e", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Provides a list of PHP built-in functions that operate on resources", + "homepage": "https://www.github.com/sebastianbergmann/resource-operations", + "support": { + "source": "https://github.com/sebastianbergmann/resource-operations/tree/3.0.4" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-03-14T16:00:52+00:00" + }, + { + "name": "sebastian/type", + "version": "3.2.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/type.git", + "reference": "75e2c2a32f5e0b3aef905b9ed0b179b953b3d7c7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/type/zipball/75e2c2a32f5e0b3aef905b9ed0b179b953b3d7c7", + "reference": "75e2c2a32f5e0b3aef905b9ed0b179b953b3d7c7", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.5" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.2-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Collection of value objects that represent the types of the PHP type system", + "homepage": "https://github.com/sebastianbergmann/type", + "support": { + "issues": "https://github.com/sebastianbergmann/type/issues", + "source": "https://github.com/sebastianbergmann/type/tree/3.2.1" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2023-02-03T06:13:03+00:00" + }, + { + "name": "sebastian/version", + "version": "3.0.2", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/version.git", + "reference": "c6c1022351a901512170118436c764e473f6de8c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/c6c1022351a901512170118436c764e473f6de8c", + "reference": "c6c1022351a901512170118436c764e473f6de8c", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library that helps with managing the version number of Git-hosted PHP projects", + "homepage": "https://github.com/sebastianbergmann/version", + "support": { + "issues": "https://github.com/sebastianbergmann/version/issues", + "source": "https://github.com/sebastianbergmann/version/tree/3.0.2" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-09-28T06:39:44+00:00" + }, + { + "name": "squizlabs/php_codesniffer", + "version": "3.10.2", + "source": { + "type": "git", + "url": "https://github.com/PHPCSStandards/PHP_CodeSniffer.git", + "reference": "86e5f5dd9a840c46810ebe5ff1885581c42a3017" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/PHPCSStandards/PHP_CodeSniffer/zipball/86e5f5dd9a840c46810ebe5ff1885581c42a3017", + "reference": "86e5f5dd9a840c46810ebe5ff1885581c42a3017", + "shasum": "" + }, + "require": { + "ext-simplexml": "*", + "ext-tokenizer": "*", + "ext-xmlwriter": "*", + "php": ">=5.4.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.0 || ^5.0 || ^6.0 || ^7.0 || ^8.0 || ^9.3.4" + }, + "bin": [ + "bin/phpcbf", + "bin/phpcs" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.x-dev" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Greg Sherwood", + "role": "Former lead" + }, + { + "name": "Juliette Reinders Folmer", + "role": "Current lead" + }, + { + "name": "Contributors", + "homepage": "https://github.com/PHPCSStandards/PHP_CodeSniffer/graphs/contributors" + } + ], + "description": "PHP_CodeSniffer tokenizes PHP, JavaScript and CSS files and detects violations of a defined set of coding standards.", + "homepage": "https://github.com/PHPCSStandards/PHP_CodeSniffer", + "keywords": [ + "phpcs", + "standards", + "static analysis" + ], + "support": { + "issues": "https://github.com/PHPCSStandards/PHP_CodeSniffer/issues", + "security": "https://github.com/PHPCSStandards/PHP_CodeSniffer/security/policy", + "source": "https://github.com/PHPCSStandards/PHP_CodeSniffer", + "wiki": "https://github.com/PHPCSStandards/PHP_CodeSniffer/wiki" + }, + "funding": [ + { + "url": "https://github.com/PHPCSStandards", + "type": "github" + }, + { + "url": "https://github.com/jrfnl", + "type": "github" + }, + { + "url": "https://opencollective.com/php_codesniffer", + "type": "open_collective" + } + ], + "time": "2024-07-21T23:26:44+00:00" + }, + { + "name": "theseer/tokenizer", + "version": "1.2.3", + "source": { + "type": "git", + "url": "https://github.com/theseer/tokenizer.git", + "reference": "737eda637ed5e28c3413cb1ebe8bb52cbf1ca7a2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/theseer/tokenizer/zipball/737eda637ed5e28c3413cb1ebe8bb52cbf1ca7a2", + "reference": "737eda637ed5e28c3413cb1ebe8bb52cbf1ca7a2", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-tokenizer": "*", + "ext-xmlwriter": "*", + "php": "^7.2 || ^8.0" + }, + "type": "library", + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Arne Blankerts", + "email": "arne@blankerts.de", + "role": "Developer" + } + ], + "description": "A small library for converting tokenized PHP source code into XML and potentially other formats", + "support": { + "issues": "https://github.com/theseer/tokenizer/issues", + "source": "https://github.com/theseer/tokenizer/tree/1.2.3" + }, + "funding": [ + { + "url": "https://github.com/theseer", + "type": "github" + } + ], + "time": "2024-03-03T12:36:25+00:00" + }, + { + "name": "webmozart/assert", + "version": "1.11.0", + "source": { + "type": "git", + "url": "https://github.com/webmozarts/assert.git", + "reference": "11cb2199493b2f8a3b53e7f19068fc6aac760991" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/webmozarts/assert/zipball/11cb2199493b2f8a3b53e7f19068fc6aac760991", + "reference": "11cb2199493b2f8a3b53e7f19068fc6aac760991", + "shasum": "" + }, + "require": { + "ext-ctype": "*", + "php": "^7.2 || ^8.0" + }, + "conflict": { + "phpstan/phpstan": "<0.12.20", + "vimeo/psalm": "<4.6.1 || 4.6.2" + }, + "require-dev": { + "phpunit/phpunit": "^8.5.13" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.10-dev" + } + }, + "autoload": { + "psr-4": { + "Webmozart\\Assert\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Bernhard Schussek", + "email": "bschussek@gmail.com" + } + ], + "description": "Assertions to validate method input/output with nice error messages.", + "keywords": [ + "assert", + "check", + "validate" + ], + "support": { + "issues": "https://github.com/webmozarts/assert/issues", + "source": "https://github.com/webmozarts/assert/tree/1.11.0" + }, + "time": "2022-06-03T18:03:27+00:00" + } + ], + "aliases": [], + "minimum-stability": "stable", + "stability-flags": [], + "prefer-stable": false, + "prefer-lowest": false, + "platform": { + "php": "^7.4 || ^8.0", + "ext-json": "*", + "ext-pdo": "*" + }, + "platform-dev": [], + "plugin-api-version": "2.3.0" +} diff --git a/src/api/v2/index.php b/src/api/v2/index.php index ef3a6b0ab..42240c00f 100644 --- a/src/api/v2/index.php +++ b/src/api/v2/index.php @@ -36,6 +36,8 @@ use Tuupola\Middleware\HttpBasicAuthentication\AuthenticatorInterface; use Tuupola\Middleware\CorsMiddleware; +use Middlewares\DeflateEncoder; + use Skeleton\Application\Response\UnauthorizedResponse; use Psr\Http\Message\ResponseInterface; @@ -154,7 +156,7 @@ public function process(Request $request, RequestHandler $handler): Response { $contentType = $request->getHeaderLine('Content-Type'); - if (strstr($contentType, 'application/json')) { + if (strstr($contentType, 'application/json') || strstr($contentType, 'application/vnd.api+json')) { $contents = json_decode(file_get_contents('php://input'), true); if (json_last_error() === JSON_ERROR_NONE) { $request = $request->withParsedBody($contents); @@ -225,11 +227,9 @@ public function process(Request $request, RequestHandler $handler): Response { $app->add("JwtAuthentication"); $app->add(new TokenAsParameterMiddleware()); $app->add(new ContentLengthMiddleware()); // NOTE: Add any middleware which may modify the response body before adding the ContentLengthMiddleware - -// NOTE: The ErrorMiddleware should be added after any middleware which may modify the response body -$errorMiddleware = $app->addErrorMiddleware(true, true, true); -$errorHandler = $errorMiddleware->getDefaultErrorHandler(); -$errorHandler->forceContentType('application/json'); +$app->add((new DeflateEncoder())->contentType( + '/^(image\/svg\\+xml|text\/.*|application\/json|"application\/vnd\.api+json)(;.*)?$/' +)); $app->add(new CorsHackMiddleware()); // NOTE: The RoutingMiddleware should be added after our CORS middleware so routing is performed first $app->addRoutingMiddleware(); @@ -281,4 +281,9 @@ public function process(Request $request, RequestHandler $handler): Response { require __DIR__ . "/../../inc/apiv2/helper/setUserPassword.routes.php"; require __DIR__ . "/../../inc/apiv2/helper/unassignAgent.routes.php"; +// NOTE: The ErrorMiddleware should be added after any middleware which may modify the response body +$errorMiddleware = $app->addErrorMiddleware(true, true, true); +$errorHandler = $errorMiddleware->getDefaultErrorHandler(); +$errorHandler->forceContentType('application/json'); + $app->run(); diff --git a/src/dba/AbstractModelFactory.class.php b/src/dba/AbstractModelFactory.class.php index c6c596e84..e1326d16f 100755 --- a/src/dba/AbstractModelFactory.class.php +++ b/src/dba/AbstractModelFactory.class.php @@ -658,6 +658,10 @@ public function filter($options, $single = false) { $options['order'] = $orderOptions; } $query .= $this->applyOrder($options['order']); + + if (array_key_exists("limit", $options)) { + $query .= $this->applyLimit($options['limit']); + } $dbh = self::getDB(); $stmt = $dbh->prepare($query); @@ -720,6 +724,12 @@ private function applyOrder($orders) { } return " ORDER BY " . implode(", ", $orderQueries); } + + //applylimit is slightly different than the other apply functions, since you can only limit by a single value + //the $limit argument is a single object LimitFilter object instead of an array of objects. + private function applyLimit($limit) { + return " LIMIT " . $limit->getQueryString(); + } private function applyGroups($groups) { $groupsQueries = array(); diff --git a/src/dba/Factory.class.php b/src/dba/Factory.class.php index 76c7d02fe..8a38f879d 100644 --- a/src/dba/Factory.class.php +++ b/src/dba/Factory.class.php @@ -493,4 +493,5 @@ public static function getHashlistHashlistFactory() { const ORDER = "order"; const UPDATE = "update"; const GROUP = "group"; + const LIMIT = "limit"; } diff --git a/src/dba/Limit.class.php b/src/dba/Limit.class.php new file mode 100644 index 000000000..4d26ecf4c --- /dev/null +++ b/src/dba/Limit.class.php @@ -0,0 +1,11 @@ +limit = intval($limit); + $this->offset = $offset !== null ? intval($offset) : null; +} + + function getQueryString() { + $queryString = $this->limit; + if ($this->offset != null) { + $queryString = $queryString . " OFFSET " . $this->offset; + } + return $queryString; + } +} + + diff --git a/src/dba/init.php b/src/dba/init.php index 99bb86bff..eb8ef1a11 100644 --- a/src/dba/init.php +++ b/src/dba/init.php @@ -13,12 +13,14 @@ require_once(dirname(__FILE__) . "/Order.class.php"); require_once(dirname(__FILE__) . "/Join.class.php"); require_once(dirname(__FILE__) . "/Group.class.php"); +require_once(dirname(__FILE__) . "/Limit.class.php"); require_once(dirname(__FILE__) . "/ComparisonFilter.class.php"); require_once(dirname(__FILE__) . "/ContainFilter.class.php"); require_once(dirname(__FILE__) . "/JoinFilter.class.php"); require_once(dirname(__FILE__) . "/OrderFilter.class.php"); require_once(dirname(__FILE__) . "/QueryFilter.class.php"); require_once(dirname(__FILE__) . "/GroupFilter.class.php"); +require_once(dirname(__FILE__) . "/LimitFilter.class.php"); require_once(dirname(__FILE__) . "/Util.class.php"); require_once(dirname(__FILE__) . "/UpdateSet.class.php"); require_once(dirname(__FILE__) . "/MassUpdateSet.class.php"); diff --git a/src/dba/models.py b/src/dba/models.py new file mode 100644 index 000000000..d0d63c3c7 --- /dev/null +++ b/src/dba/models.py @@ -0,0 +1,614 @@ +# This is an auto-generated Django model module. +# You'll have to do the following manually to clean this up: +# * Rearrange models' order +# * Make sure each model has one field with primary_key=True +# * Make sure each ForeignKey and OneToOneField has `on_delete` set to the desired behavior +# * Remove `managed = False` lines if you wish to allow Django to create, modify, and delete the table +# Feel free to rename the models, but don't rename db_table values or field names. +#from django.db import models + +class models: + DO_NOTHING = 'do_nothing' + class Field: + def __init__(self, **kwargs): + pass + class Model: + pass + class AutoField(Field): + pass + + class CharField(Field): + pass + class IntegerField(Field): + pass + class TextField(Field): + pass + class BigIntegerField(Field): + pass + class ForeignKey(Field): + def __init__(self, related_model, on_cascade, **kwargs): + pass + +class Accessgroup(models.Model): + accessgroupid = models.AutoField(db_column='accessGroupId', primary_key=True) + groupname = models.CharField(db_column='groupName', max_length=50) + + class Meta: + managed = False + db_table = 'AccessGroup' + + +class Accessgroupagent(models.Model): + accessgroupagentid = models.AutoField(db_column='accessGroupAgentId', primary_key=True) + accessgroupid = models.ForeignKey(Accessgroup, models.DO_NOTHING, db_column='accessGroupId') + agentid = models.ForeignKey('Agent', models.DO_NOTHING, db_column='agentId') + + class Meta: + managed = False + db_table = 'AccessGroupAgent' + + +class Accessgroupuser(models.Model): + accessgroupuserid = models.AutoField(db_column='accessGroupUserId', primary_key=True) + accessgroupid = models.ForeignKey(Accessgroup, models.DO_NOTHING, db_column='accessGroupId') + userid = models.ForeignKey('User', models.DO_NOTHING, db_column='userId') + + class Meta: + managed = False + db_table = 'AccessGroupUser' + + +class Agent(models.Model): + agentid = models.AutoField(db_column='agentId', primary_key=True) + agentname = models.CharField(db_column='agentName', max_length=100) + uid = models.CharField(max_length=100) + os = models.IntegerField() + devices = models.TextField() + cmdpars = models.CharField(db_column='cmdPars', max_length=256) + ignoreerrors = models.IntegerField(db_column='ignoreErrors') + isactive = models.IntegerField(db_column='isActive') + istrusted = models.IntegerField(db_column='isTrusted') + token = models.CharField(max_length=30) + lastact = models.CharField(db_column='lastAct', max_length=50) + lasttime = models.BigIntegerField(db_column='lastTime') + lastip = models.CharField(db_column='lastIp', max_length=50) + userid = models.ForeignKey('User', models.DO_NOTHING, db_column='userId', blank=True, null=True) + cpuonly = models.IntegerField(db_column='cpuOnly') + clientsignature = models.CharField(db_column='clientSignature', max_length=50) + + class Meta: + managed = False + db_table = 'Agent' + + +class Agentbinary(models.Model): + agentbinaryid = models.AutoField(db_column='agentBinaryId', primary_key=True) + type = models.CharField(max_length=20) + version = models.CharField(max_length=20) + operatingsystems = models.CharField(db_column='operatingSystems', max_length=50) + filename = models.CharField(max_length=50) + updatetrack = models.CharField(db_column='updateTrack', max_length=20) + updateavailable = models.CharField(db_column='updateAvailable', max_length=20) + + class Meta: + managed = False + db_table = 'AgentBinary' + + +class Agenterror(models.Model): + agenterrorid = models.AutoField(db_column='agentErrorId', primary_key=True) + agentid = models.ForeignKey(Agent, models.DO_NOTHING, db_column='agentId') + taskid = models.ForeignKey('Task', models.DO_NOTHING, db_column='taskId', blank=True, null=True) + time = models.BigIntegerField() + error = models.TextField() + chunkid = models.IntegerField(db_column='chunkId', blank=True, null=True) + + class Meta: + managed = False + db_table = 'AgentError' + + +class Agentstat(models.Model): + agentstatid = models.AutoField(db_column='agentStatId', primary_key=True) + agentid = models.ForeignKey(Agent, models.DO_NOTHING, db_column='agentId') + stattype = models.IntegerField(db_column='statType') + time = models.BigIntegerField() + value = models.CharField(max_length=128) + + class Meta: + managed = False + db_table = 'AgentStat' + + +class Agentzap(models.Model): + agentzapid = models.AutoField(db_column='agentZapId', primary_key=True) + agentid = models.ForeignKey(Agent, models.DO_NOTHING, db_column='agentId') + lastzapid = models.ForeignKey('Zap', models.DO_NOTHING, db_column='lastZapId', blank=True, null=True) + + class Meta: + managed = False + db_table = 'AgentZap' + + +class Apigroup(models.Model): + apigroupid = models.AutoField(db_column='apiGroupId', primary_key=True) + name = models.CharField(max_length=100) + permissions = models.TextField() + + class Meta: + managed = False + db_table = 'ApiGroup' + + +class Apikey(models.Model): + apikeyid = models.AutoField(db_column='apiKeyId', primary_key=True) + startvalid = models.BigIntegerField(db_column='startValid') + endvalid = models.BigIntegerField(db_column='endValid') + accesskey = models.CharField(db_column='accessKey', max_length=256) + accesscount = models.IntegerField(db_column='accessCount') + userid = models.ForeignKey('User', models.DO_NOTHING, db_column='userId') + apigroupid = models.ForeignKey(Apigroup, models.DO_NOTHING, db_column='apiGroupId') + + class Meta: + managed = False + db_table = 'ApiKey' + + +class Assignment(models.Model): + assignmentid = models.AutoField(db_column='assignmentId', primary_key=True) + taskid = models.ForeignKey('Task', models.DO_NOTHING, db_column='taskId') + agentid = models.ForeignKey(Agent, models.DO_NOTHING, db_column='agentId') + benchmark = models.CharField(max_length=50) + + class Meta: + managed = False + db_table = 'Assignment' + + +class Chunk(models.Model): + chunkid = models.AutoField(db_column='chunkId', primary_key=True) + taskid = models.ForeignKey('Task', models.DO_NOTHING, db_column='taskId') + skip = models.PositiveBigIntegerField() + length = models.PositiveBigIntegerField() + agentid = models.ForeignKey(Agent, models.DO_NOTHING, db_column='agentId', blank=True, null=True) + dispatchtime = models.BigIntegerField(db_column='dispatchTime') + solvetime = models.BigIntegerField(db_column='solveTime') + checkpoint = models.PositiveBigIntegerField() + progress = models.IntegerField(blank=True, null=True) + state = models.IntegerField() + cracked = models.IntegerField() + speed = models.BigIntegerField() + + class Meta: + managed = False + db_table = 'Chunk' + + +class Config(models.Model): + configid = models.AutoField(db_column='configId', primary_key=True) + configsectionid = models.ForeignKey('Configsection', models.DO_NOTHING, db_column='configSectionId') + item = models.CharField(max_length=80) + value = models.TextField() + + class Meta: + managed = False + db_table = 'Config' + + +class Configsection(models.Model): + configsectionid = models.AutoField(db_column='configSectionId', primary_key=True) + sectionname = models.CharField(db_column='sectionName', max_length=100) + + class Meta: + managed = False + db_table = 'ConfigSection' + + +class Crackerbinary(models.Model): + crackerbinaryid = models.AutoField(db_column='crackerBinaryId', primary_key=True) + crackerbinarytypeid = models.ForeignKey('Crackerbinarytype', models.DO_NOTHING, db_column='crackerBinaryTypeId') + version = models.CharField(max_length=20) + downloadurl = models.CharField(db_column='downloadUrl', max_length=150) + binaryname = models.CharField(db_column='binaryName', max_length=50) + + class Meta: + managed = False + db_table = 'CrackerBinary' + + +class Crackerbinarytype(models.Model): + crackerbinarytypeid = models.AutoField(db_column='crackerBinaryTypeId', primary_key=True) + typename = models.CharField(db_column='typeName', max_length=30) + ischunkingavailable = models.IntegerField(db_column='isChunkingAvailable') + + class Meta: + managed = False + db_table = 'CrackerBinaryType' + + +class File(models.Model): + fileid = models.AutoField(db_column='fileId', primary_key=True) + filename = models.CharField(max_length=100) + size = models.BigIntegerField() + issecret = models.IntegerField(db_column='isSecret') + filetype = models.IntegerField(db_column='fileType') + accessgroupid = models.ForeignKey(Accessgroup, models.DO_NOTHING, db_column='accessGroupId') + linecount = models.BigIntegerField(db_column='lineCount', blank=True, null=True) + + class Meta: + managed = False + db_table = 'File' + + +class Filedelete(models.Model): + filedeleteid = models.AutoField(db_column='fileDeleteId', primary_key=True) + filename = models.CharField(max_length=256) + time = models.BigIntegerField() + + class Meta: + managed = False + db_table = 'FileDelete' + + +class Filedownload(models.Model): + filedownloadid = models.AutoField(db_column='fileDownloadId', primary_key=True) + time = models.BigIntegerField() + fileid = models.ForeignKey(File, models.DO_NOTHING, db_column='fileId') + status = models.IntegerField() + + class Meta: + managed = False + db_table = 'FileDownload' + + +class Filepretask(models.Model): + filepretaskid = models.AutoField(db_column='filePretaskId', primary_key=True) + fileid = models.ForeignKey(File, models.DO_NOTHING, db_column='fileId') + pretaskid = models.ForeignKey('Pretask', models.DO_NOTHING, db_column='pretaskId') + + class Meta: + managed = False + db_table = 'FilePretask' + + +class Filetask(models.Model): + filetaskid = models.AutoField(db_column='fileTaskId', primary_key=True) + fileid = models.ForeignKey(File, models.DO_NOTHING, db_column='fileId') + taskid = models.ForeignKey('Task', models.DO_NOTHING, db_column='taskId') + + class Meta: + managed = False + db_table = 'FileTask' + + +class Hash(models.Model): + hashid = models.AutoField(db_column='hashId', primary_key=True) + hashlistid = models.ForeignKey('Hashlist', models.DO_NOTHING, db_column='hashlistId') + hash = models.TextField() + salt = models.CharField(max_length=256, blank=True, null=True) + plaintext = models.CharField(max_length=256, blank=True, null=True) + timecracked = models.BigIntegerField(db_column='timeCracked', blank=True, null=True) + chunkid = models.ForeignKey(Chunk, models.DO_NOTHING, db_column='chunkId', blank=True, null=True) + iscracked = models.IntegerField(db_column='isCracked') + crackpos = models.BigIntegerField(db_column='crackPos') + + class Meta: + managed = False + db_table = 'Hash' + + +class Hashbinary(models.Model): + hashbinaryid = models.AutoField(db_column='hashBinaryId', primary_key=True) + hashlistid = models.ForeignKey('Hashlist', models.DO_NOTHING, db_column='hashlistId') + essid = models.CharField(max_length=100) + hash = models.TextField() + plaintext = models.CharField(max_length=1024, blank=True, null=True) + timecracked = models.BigIntegerField(db_column='timeCracked', blank=True, null=True) + chunkid = models.ForeignKey(Chunk, models.DO_NOTHING, db_column='chunkId', blank=True, null=True) + iscracked = models.IntegerField(db_column='isCracked') + crackpos = models.BigIntegerField(db_column='crackPos') + + class Meta: + managed = False + db_table = 'HashBinary' + + +class Hashtype(models.Model): + hashtypeid = models.IntegerField(db_column='hashTypeId', primary_key=True) + description = models.CharField(max_length=256) + issalted = models.IntegerField(db_column='isSalted') + isslowhash = models.IntegerField(db_column='isSlowHash') + + class Meta: + managed = False + db_table = 'HashType' + + +class Hashlist(models.Model): + hashlistid = models.AutoField(db_column='hashlistId', primary_key=True) + hashlistname = models.CharField(db_column='hashlistName', max_length=100) + format = models.IntegerField() + hashtypeid = models.ForeignKey(Hashtype, models.DO_NOTHING, db_column='hashTypeId') + hashcount = models.IntegerField(db_column='hashCount') + saltseparator = models.CharField(db_column='saltSeparator', max_length=10, blank=True, null=True) + cracked = models.IntegerField() + issecret = models.IntegerField(db_column='isSecret') + hexsalt = models.IntegerField(db_column='hexSalt') + issalted = models.IntegerField(db_column='isSalted') + accessgroupid = models.ForeignKey(Accessgroup, models.DO_NOTHING, db_column='accessGroupId') + notes = models.TextField() + brainid = models.IntegerField(db_column='brainId') + brainfeatures = models.IntegerField(db_column='brainFeatures') + isarchived = models.IntegerField(db_column='isArchived') + + class Meta: + managed = False + db_table = 'Hashlist' + + +class Hashlisthashlist(models.Model): + hashlisthashlistid = models.AutoField(db_column='hashlistHashlistId', primary_key=True) + parenthashlistid = models.ForeignKey(Hashlist, models.DO_NOTHING, db_column='parentHashlistId') + hashlistid = models.ForeignKey(Hashlist, models.DO_NOTHING, db_column='hashlistId') + + class Meta: + managed = False + db_table = 'HashlistHashlist' + + +class Healthcheck(models.Model): + healthcheckid = models.AutoField(db_column='healthCheckId', primary_key=True) + time = models.BigIntegerField() + status = models.IntegerField() + checktype = models.IntegerField(db_column='checkType') + hashtypeid = models.IntegerField(db_column='hashtypeId') + crackerbinaryid = models.ForeignKey(Crackerbinary, models.DO_NOTHING, db_column='crackerBinaryId') + expectedcracks = models.IntegerField(db_column='expectedCracks') + attackcmd = models.CharField(db_column='attackCmd', max_length=256) + + class Meta: + managed = False + db_table = 'HealthCheck' + + +class Healthcheckagent(models.Model): + healthcheckagentid = models.AutoField(db_column='healthCheckAgentId', primary_key=True) + healthcheckid = models.ForeignKey(Healthcheck, models.DO_NOTHING, db_column='healthCheckId') + agentid = models.ForeignKey(Agent, models.DO_NOTHING, db_column='agentId') + status = models.IntegerField() + cracked = models.IntegerField() + numgpus = models.IntegerField(db_column='numGpus') + start = models.BigIntegerField() + end = models.BigIntegerField() + errors = models.TextField() + + class Meta: + managed = False + db_table = 'HealthCheckAgent' + + +class Logentry(models.Model): + logentryid = models.AutoField(db_column='logEntryId', primary_key=True) + issuer = models.CharField(max_length=50) + issuerid = models.CharField(db_column='issuerId', max_length=50) + level = models.CharField(max_length=50) + message = models.TextField() + time = models.BigIntegerField() + + class Meta: + managed = False + db_table = 'LogEntry' + + +class Notificationsetting(models.Model): + notificationsettingid = models.AutoField(db_column='notificationSettingId', primary_key=True) + action = models.CharField(max_length=50) + objectid = models.IntegerField(db_column='objectId', blank=True, null=True) + notification = models.CharField(max_length=50) + userid = models.ForeignKey('User', models.DO_NOTHING, db_column='userId') + receiver = models.CharField(max_length=256) + isactive = models.IntegerField(db_column='isActive') + + class Meta: + managed = False + db_table = 'NotificationSetting' + + +class Preprocessor(models.Model): + preprocessorid = models.AutoField(db_column='preprocessorId', primary_key=True) + name = models.CharField(max_length=256) + url = models.CharField(max_length=512) + binaryname = models.CharField(db_column='binaryName', max_length=256) + keyspacecommand = models.CharField(db_column='keyspaceCommand', max_length=256, blank=True, null=True) + skipcommand = models.CharField(db_column='skipCommand', max_length=256, blank=True, null=True) + limitcommand = models.CharField(db_column='limitCommand', max_length=256, blank=True, null=True) + + class Meta: + managed = False + db_table = 'Preprocessor' + + +class Pretask(models.Model): + pretaskid = models.AutoField(db_column='pretaskId', primary_key=True) + taskname = models.CharField(db_column='taskName', max_length=100) + attackcmd = models.CharField(db_column='attackCmd', max_length=256) + chunktime = models.IntegerField(db_column='chunkTime') + statustimer = models.IntegerField(db_column='statusTimer') + color = models.CharField(max_length=20, blank=True, null=True) + issmall = models.IntegerField(db_column='isSmall') + iscputask = models.IntegerField(db_column='isCpuTask') + usenewbench = models.IntegerField(db_column='useNewBench') + priority = models.IntegerField() + maxagents = models.IntegerField(db_column='maxAgents') + ismaskimport = models.IntegerField(db_column='isMaskImport') + crackerbinarytypeid = models.ForeignKey(Crackerbinarytype, models.DO_NOTHING, db_column='crackerBinaryTypeId') + + class Meta: + managed = False + db_table = 'Pretask' + + +class Regvoucher(models.Model): + regvoucherid = models.AutoField(db_column='regVoucherId', primary_key=True) + voucher = models.CharField(max_length=100) + time = models.BigIntegerField() + + class Meta: + managed = False + db_table = 'RegVoucher' + + +class Rightgroup(models.Model): + rightgroupid = models.AutoField(db_column='rightGroupId', primary_key=True) + groupname = models.CharField(db_column='groupName', max_length=50) + permissions = models.TextField() + + class Meta: + managed = False + db_table = 'RightGroup' + + +class Session(models.Model): + sessionid = models.AutoField(db_column='sessionId', primary_key=True) + userid = models.ForeignKey('User', models.DO_NOTHING, db_column='userId') + sessionstartdate = models.BigIntegerField(db_column='sessionStartDate') + lastactiondate = models.BigIntegerField(db_column='lastActionDate') + isopen = models.IntegerField(db_column='isOpen') + sessionlifetime = models.IntegerField(db_column='sessionLifetime') + sessionkey = models.CharField(db_column='sessionKey', max_length=256) + + class Meta: + managed = False + db_table = 'Session' + + +class Speed(models.Model): + speedid = models.AutoField(db_column='speedId', primary_key=True) + agentid = models.ForeignKey(Agent, models.DO_NOTHING, db_column='agentId') + taskid = models.ForeignKey('Task', models.DO_NOTHING, db_column='taskId') + speed = models.BigIntegerField() + time = models.BigIntegerField() + + class Meta: + managed = False + db_table = 'Speed' + + +class Storedvalue(models.Model): + storedvalueid = models.CharField(db_column='storedValueId', primary_key=True, max_length=50) + val = models.CharField(max_length=256) + + class Meta: + managed = False + db_table = 'StoredValue' + + +class Supertask(models.Model): + supertaskid = models.AutoField(db_column='supertaskId', primary_key=True) + supertaskname = models.CharField(db_column='supertaskName', max_length=50) + + class Meta: + managed = False + db_table = 'Supertask' + + +class Supertaskpretask(models.Model): + supertaskpretaskid = models.AutoField(db_column='supertaskPretaskId', primary_key=True) + supertaskid = models.ForeignKey(Supertask, models.DO_NOTHING, db_column='supertaskId') + pretaskid = models.ForeignKey(Pretask, models.DO_NOTHING, db_column='pretaskId') + + class Meta: + managed = False + db_table = 'SupertaskPretask' + + +class Task(models.Model): + taskid = models.AutoField(db_column='taskId', primary_key=True) + taskname = models.CharField(db_column='taskName', max_length=256) + attackcmd = models.CharField(db_column='attackCmd', max_length=256) + chunktime = models.IntegerField(db_column='chunkTime') + statustimer = models.IntegerField(db_column='statusTimer') + keyspace = models.BigIntegerField() + keyspaceprogress = models.BigIntegerField(db_column='keyspaceProgress') + priority = models.IntegerField() + maxagents = models.IntegerField(db_column='maxAgents') + color = models.CharField(max_length=20, blank=True, null=True) + issmall = models.IntegerField(db_column='isSmall') + iscputask = models.IntegerField(db_column='isCpuTask') + usenewbench = models.IntegerField(db_column='useNewBench') + skipkeyspace = models.BigIntegerField(db_column='skipKeyspace') + crackerbinaryid = models.ForeignKey(Crackerbinary, models.DO_NOTHING, db_column='crackerBinaryId', blank=True, null=True) + crackerbinarytypeid = models.ForeignKey(Crackerbinarytype, models.DO_NOTHING, db_column='crackerBinaryTypeId', blank=True, null=True) + taskwrapperid = models.ForeignKey('Taskwrapper', models.DO_NOTHING, db_column='taskWrapperId') + isarchived = models.IntegerField(db_column='isArchived') + notes = models.TextField() + staticchunks = models.IntegerField(db_column='staticChunks') + chunksize = models.BigIntegerField(db_column='chunkSize') + forcepipe = models.IntegerField(db_column='forcePipe') + usepreprocessor = models.IntegerField(db_column='usePreprocessor') + preprocessorcommand = models.CharField(db_column='preprocessorCommand', max_length=256) + + class Meta: + managed = False + db_table = 'Task' + + +class Taskdebugoutput(models.Model): + taskdebugoutputid = models.AutoField(db_column='taskDebugOutputId', primary_key=True) + taskid = models.ForeignKey(Task, models.DO_NOTHING, db_column='taskId') + output = models.CharField(max_length=256) + + class Meta: + managed = False + db_table = 'TaskDebugOutput' + + +class Taskwrapper(models.Model): + taskwrapperid = models.AutoField(db_column='taskWrapperId', primary_key=True) + priority = models.IntegerField() + maxagents = models.IntegerField(db_column='maxAgents') + tasktype = models.IntegerField(db_column='taskType') + hashlistid = models.ForeignKey(Hashlist, models.DO_NOTHING, db_column='hashlistId') + accessgroupid = models.ForeignKey(Accessgroup, models.DO_NOTHING, db_column='accessGroupId', blank=True, null=True) + taskwrappername = models.CharField(db_column='taskWrapperName', max_length=100) + isarchived = models.IntegerField(db_column='isArchived') + cracked = models.IntegerField() + + class Meta: + managed = False + db_table = 'TaskWrapper' + + +class User(models.Model): + userid = models.AutoField(db_column='userId', primary_key=True) + username = models.CharField(unique=True, max_length=100) + email = models.CharField(max_length=150) + passwordhash = models.CharField(db_column='passwordHash', max_length=256) + passwordsalt = models.CharField(db_column='passwordSalt', max_length=256) + isvalid = models.IntegerField(db_column='isValid') + iscomputedpassword = models.IntegerField(db_column='isComputedPassword') + lastlogindate = models.BigIntegerField(db_column='lastLoginDate') + registeredsince = models.BigIntegerField(db_column='registeredSince') + sessionlifetime = models.IntegerField(db_column='sessionLifetime') + rightgroupid = models.ForeignKey(Rightgroup, models.DO_NOTHING, db_column='rightGroupId') + yubikey = models.CharField(max_length=256, blank=True, null=True) + otp1 = models.CharField(max_length=256, blank=True, null=True) + otp2 = models.CharField(max_length=256, blank=True, null=True) + otp3 = models.CharField(max_length=256, blank=True, null=True) + otp4 = models.CharField(max_length=256, blank=True, null=True) + + class Meta: + managed = False + db_table = 'User' + + +class Zap(models.Model): + zapid = models.AutoField(db_column='zapId', primary_key=True) + hash = models.TextField() + solvetime = models.BigIntegerField(db_column='solveTime') + agentid = models.ForeignKey(Agent, models.DO_NOTHING, db_column='agentId', blank=True, null=True) + hashlistid = models.ForeignKey(Hashlist, models.DO_NOTHING, db_column='hashlistId') + + class Meta: + managed = False + db_table = 'Zap' diff --git a/src/dba/models/Task.class.php b/src/dba/models/Task.class.php index f62750433..9635e2f62 100644 --- a/src/dba/models/Task.class.php +++ b/src/dba/models/Task.class.php @@ -96,7 +96,7 @@ static function getFeatures() { $dict['keyspaceProgress'] = ['read_only' => True, "type" => "int64", "subtype" => "unset", "choices" => "unset", "null" => False, "pk" => False, "protected" => True, "private" => False, "alias" => "keyspaceProgress"]; $dict['priority'] = ['read_only' => False, "type" => "int", "subtype" => "unset", "choices" => "unset", "null" => False, "pk" => False, "protected" => False, "private" => False, "alias" => "priority"]; $dict['maxAgents'] = ['read_only' => False, "type" => "int", "subtype" => "unset", "choices" => "unset", "null" => False, "pk" => False, "protected" => False, "private" => False, "alias" => "maxAgents"]; - $dict['color'] = ['read_only' => False, "type" => "str(50)", "subtype" => "unset", "choices" => "unset", "null" => True, "pk" => False, "protected" => False, "private" => False, "alias" => "color"]; + $dict['color'] = ['read_only' => False, "type" => "str(20)", "subtype" => "unset", "choices" => "unset", "null" => True, "pk" => False, "protected" => False, "private" => False, "alias" => "color"]; $dict['isSmall'] = ['read_only' => False, "type" => "bool", "subtype" => "unset", "choices" => "unset", "null" => False, "pk" => False, "protected" => False, "private" => False, "alias" => "isSmall"]; $dict['isCpuTask'] = ['read_only' => False, "type" => "bool", "subtype" => "unset", "choices" => "unset", "null" => False, "pk" => False, "protected" => False, "private" => False, "alias" => "isCpuTask"]; $dict['useNewBench'] = ['read_only' => True, "type" => "bool", "subtype" => "unset", "choices" => "unset", "null" => False, "pk" => False, "protected" => False, "private" => False, "alias" => "useNewBench"]; diff --git a/src/dba/models/generator.php b/src/dba/models/generator.php index 870589a8a..77086d171 100644 --- a/src/dba/models/generator.php +++ b/src/dba/models/generator.php @@ -64,7 +64,7 @@ ['name' => 'lastAct', 'read_only' => True, 'type' => 'str(50)', 'protected' => True], ['name' => 'lastTime', 'read_only' => True, 'type' => 'int64', 'protected' => True], ['name' => 'lastIp', 'read_only' => True, 'type' => 'str(50)', 'protected' => True], - ['name' => 'userId', 'read_only' => False, 'type' => 'int', 'null' => True], + ['name' => 'userId', 'read_only' => False, 'type' => 'int', 'null' => True, 'relation' => 'User'], ['name' => 'cpuOnly', 'read_only' => False, 'type' => 'bool'], ['name' => 'clientSignature', 'read_only' => False, 'type' => 'str(50)'], ], @@ -83,9 +83,9 @@ $CONF['AgentError'] = [ 'columns' => [ ['name' => 'agentErrorId', 'read_only' => True, 'type' => 'int', 'protected' => True], - ['name' => 'agentId', 'read_only' => True, 'type' => 'int', 'protected' => True], - ['name' => 'taskId', 'read_only' => True, 'type' => 'int', 'protected' => True], - ['name' => 'chunkId', 'read_only' => True, 'type' => 'int', 'protected' => True], + ['name' => 'agentId', 'read_only' => True, 'type' => 'int', 'protected' => True, 'relation' => 'Agent'], + ['name' => 'taskId', 'read_only' => True, 'type' => 'int', 'protected' => True, 'relation' => 'Task'], + ['name' => 'chunkId', 'read_only' => True, 'type' => 'int', 'protected' => True, 'relation' => 'Chunk'], ['name' => 'time', 'read_only' => True, 'type' => 'int64', 'protected' => True], ['name' => 'error', 'read_only' => True, 'type' => 'str(65535)', 'protected' => True], ], @@ -93,7 +93,7 @@ $CONF['AgentStat'] = [ 'columns' => [ ['name' => 'agentStatId', 'read_only' => True, 'type' => 'int', 'protected' => True], - ['name' => 'agentId', 'read_only' => True, 'protected' => True, 'type' => 'int', 'protected' => True], + ['name' => 'agentId', 'read_only' => True, 'protected' => True, 'type' => 'int', 'protected' => True, 'relation' => 'Agent'], ['name' => 'statType', 'read_only' => True, 'protected' => True, 'type' => 'int', 'protected' => True], ['name' => 'time', 'read_only' => True, 'protected' => True, 'type' => 'int64', 'protected' => True], ['name' => 'value', 'read_only' => True, 'protected' => True, 'type' => 'array', 'subtype' => 'int', 'protected' => True], @@ -102,7 +102,7 @@ $CONF['AgentZap'] = [ 'columns' => [ ['name' => 'agentZapId', 'read_only' => True, 'type' => 'int', 'protected' => True], - ['name' => 'agentId', 'read_only' => True, 'type' => 'int', 'protected' => True], + ['name' => 'agentId', 'read_only' => True, 'type' => 'int', 'protected' => True, 'relation' => 'Agent'], ['name' => 'lastZapId', 'read_only' => True, 'type' => 'str(128)', 'protected' => True], ], ]; @@ -113,8 +113,8 @@ ['name' => 'endValid', 'read_only' => False, 'type' => 'int64'], ['name' => 'accessKey', 'read_only' => True, 'type' => 'str(256)', 'protected' => True], ['name' => 'accessCount', 'read_only' => True, 'type' => 'int', 'protected' => True], - ['name' => 'userId', 'read_only' => False, 'type' => 'int'], - ['name' => 'apiGroupId', 'read_only' => False, 'type' => 'int'], + ['name' => 'userId', 'read_only' => False, 'type' => 'int', 'relation' => 'User'], + ['name' => 'apiGroupId', 'read_only' => False, 'type' => 'int', 'relation' => 'ApiGroup'], ], ]; $CONF['ApiGroup'] = [ @@ -128,18 +128,18 @@ 'permission_alias' => 'AgentAssignment', 'columns' => [ ['name' => 'assignmentId', 'read_only' => True, 'type' => 'int', 'protected' => True], - ['name' => 'taskId', 'read_only' => False, 'type' => 'int'], - ['name' => 'agentId', 'read_only' => False, 'type' => 'int'], + ['name' => 'taskId', 'read_only' => False, 'type' => 'int', 'relation' => 'Task'], + ['name' => 'agentId', 'read_only' => False, 'type' => 'int', 'relation' => 'Agent'], ['name' => 'benchmark', 'read_only' => True, 'type' => 'str(50)', 'protected' => True], ], ]; $CONF['Chunk'] = [ 'columns' => [ ['name' => 'chunkId', 'read_only' => True, 'type' => 'int', 'protected' => True], - ['name' => 'taskId', 'read_only' => True, 'type' => 'int', 'protected' => True], + ['name' => 'taskId', 'read_only' => True, 'type' => 'int', 'protected' => True, 'relation' => 'Task'], ['name' => 'skip', 'read_only' => True, 'type' => 'uint64', 'protected' => True], ['name' => 'length', 'read_only' => True, 'type' => 'uint64', 'protected' => True], - ['name' => 'agentId', 'read_only' => True, 'type' => 'int', 'protected' => True], + ['name' => 'agentId', 'read_only' => True, 'type' => 'int', 'protected' => True, 'relation' => 'Agent'], ['name' => 'dispatchTime', 'read_only' => True, 'type' => 'int64', 'protected' => True], ['name' => 'solveTime', 'read_only' => True, 'type' => 'int64', 'protected' => True], ['name' => 'checkpoint', 'read_only' => True, 'type' => 'int64', 'protected' => True], @@ -152,7 +152,7 @@ $CONF['Config'] = [ 'columns' => [ ['name' => 'configId', 'read_only' => True, 'type' => 'int', 'protected' => True], - ['name' => 'configSectionId', 'read_only' => False, 'type' => 'int'], + ['name' => 'configSectionId', 'read_only' => False, 'type' => 'int', 'relation' => 'ConfigSecion'], ['name' => 'item', 'read_only' => False, 'type' => 'str(128)'], ['name' => 'value', 'read_only' => False, 'type' => 'str(65535)'], ], @@ -166,7 +166,7 @@ $CONF['CrackerBinary'] = [ 'columns' => [ ['name' => 'crackerBinaryId', 'read_only' => True, 'type' => 'int', 'protected' => True], - ['name' => 'crackerBinaryTypeId', 'read_only' => False, 'type' => 'int'], + ['name' => 'crackerBinaryTypeId', 'read_only' => False, 'type' => 'int', 'relation' => 'CrackerBinaryType'], ['name' => 'version', 'read_only' => False, 'type' => 'str(20)'], ['name' => 'downloadUrl', 'read_only' => False, 'type' => 'str(150)'], ['name' => 'binaryName', 'read_only' => False, 'type' => 'str(50)'], @@ -186,7 +186,7 @@ ['name' => 'size', 'read_only' => True, 'type' => 'int64', 'protected' => True], ['name' => 'isSecret', 'read_only' => False, 'type' => 'bool'], ['name' => 'fileType', 'read_only' => False, 'type' => 'int'], - ['name' => 'accessGroupId', 'read_only' => False, 'type' => 'int'], + ['name' => 'accessGroupId', 'read_only' => False, 'type' => 'int', 'relation' => 'AccessGroup'], ['name' => 'lineCount', 'read_only' => True, 'type' => 'int64', 'protected' => True], ], ]; @@ -201,19 +201,19 @@ 'columns' => [ ['name' => 'fileDownloadId', 'read_only' => True, 'type' => 'int', 'protected' => True], ['name' => 'time', 'read_only' => True, 'type' => 'int64', 'protected' => True], - ['name' => 'fileId', 'read_only' => True, 'type' => 'int', 'protected' => True], + ['name' => 'fileId', 'read_only' => True, 'type' => 'int', 'protected' => True, 'relation' => 'File'], ['name' => 'status', 'read_only' => True, 'type' => 'int', 'protected' => True], ], ]; $CONF['Hash'] = [ 'columns' => [ ['name' => 'hashId', 'read_only' => True, 'type' => 'int', 'protected' => True], - ['name' => 'hashlistId', 'read_only' => False, 'type' => 'int'], + ['name' => 'hashlistId', 'read_only' => False, 'type' => 'int', 'relation' => 'Hashlist'], ['name' => 'hash', 'read_only' => False, 'type' => 'str(65535)'], ['name' => 'salt', 'read_only' => False, 'type' => 'str(256)'], ['name' => 'plaintext', 'read_only' => False, 'type' => 'str(256)'], ['name' => 'timeCracked', 'read_only' => False, 'type' => 'int64'], - ['name' => 'chunkId', 'read_only' => False, 'type' => 'int'], + ['name' => 'chunkId', 'read_only' => False, 'type' => 'int', 'relation' => 'Chunk'], ['name' => 'isCracked', 'read_only' => False, 'type' => 'bool'], ['name' => 'crackPos', 'read_only' => False, 'type' => 'int64'], ], @@ -221,12 +221,12 @@ $CONF['HashBinary'] = [ 'columns' => [ ['name' => 'hashBinaryId', 'read_only' => True, 'type' => 'int', 'protected' => True], - ['name' => 'hashlistId', 'read_only' => False, 'type' => 'int'], + ['name' => 'hashlistId', 'read_only' => False, 'type' => 'int', 'relation' => 'Hashlist'], ['name' => 'essid', 'read_only' => False, 'type' => 'str(100)'], ['name' => 'hash', 'read_only' => False, 'type' => 'str(4294967295)'], ['name' => 'plaintext', 'read_only' => False, 'type' => 'str(1024)'], ['name' => 'timeCracked', 'read_only' => False, 'type' => 'int64'], - ['name' => 'chunkId', 'read_only' => False, 'type' => 'int'], + ['name' => 'chunkId', 'read_only' => False, 'type' => 'int', 'relation' => 'Chunk'], ['name' => 'isCracked', 'read_only' => False, 'type' => 'bool'], ['name' => 'crackPos', 'read_only' => False, 'type' => 'int64'], ], @@ -236,14 +236,14 @@ ['name' => 'hashlistId', 'read_only' => True, 'type' => 'int', 'protected' => True], ['name' => 'hashlistName', 'read_only' => False, 'type' => 'str(100)', 'alias' => UQueryHashlist::HASHLIST_NAME], ['name' => 'format', 'read_only' => True, 'type' => 'int', 'choices' => $FieldHashlistFormatChoices], - ['name' => 'hashTypeId', 'read_only' => True, 'type' => 'int'], + ['name' => 'hashTypeId', 'read_only' => True, 'type' => 'int', 'relation' => 'HashType'], ['name' => 'hashCount', 'read_only' => True, 'type' => 'int'], ['name' => 'saltSeparator', 'read_only' => True, 'type' => 'str(10)', 'null' => True, 'alias' => UQueryHashlist::HASHLIST_SEPARATOR], ['name' => 'cracked', 'read_only' => true, 'type' => 'int', 'protected' => True], ['name' => 'isSecret', 'read_only' => False, 'type' => 'bool'], ['name' => 'hexSalt', 'read_only' => True, 'type' => 'bool', 'alias' => UQueryHashlist::HASHLIST_HEX_SALTED], ['name' => 'isSalted', 'read_only' => True, 'type' => 'bool'], - ['name' => 'accessGroupId', 'read_only' => False, 'type' => 'int'], + ['name' => 'accessGroupId', 'read_only' => False, 'type' => 'int', , 'relation' => 'AccessGroup'], ['name' => 'notes', 'read_only' => False, 'type' => 'str(65535)'], ['name' => 'brainId', 'read_only' => True, 'type' => 'bool', 'alias' => UQueryHashlist::HASHLIST_USE_BRAIN], ['name' => 'brainFeatures', 'read_only' => True, 'type' => 'int'], @@ -264,8 +264,8 @@ ['name' => 'time', 'read_only' => True, 'type' => 'int64', 'protected' => True], ['name' => 'status', 'read_only' => True, 'type' => 'int', 'protected' => True], ['name' => 'checkType', 'read_only' => False, 'type' => 'int'], - ['name' => 'hashtypeId', 'read_only' => False, 'type' => 'int'], - ['name' => 'crackerBinaryId', 'read_only' => False, 'type' => 'int'], + ['name' => 'hashtypeId', 'read_only' => False, 'type' => 'int', 'relation' => 'HashType'], + ['name' => 'crackerBinaryId', 'read_only' => False, 'type' => 'int', 'relation' => 'CrackerBinary'], ['name' => 'expectedCracks', 'read_only' => True, 'type' => 'int', 'protected' => True], ['name' => 'attackCmd', 'read_only' => True, 'type' => 'str(65535)', 'protected' => True], ], @@ -273,8 +273,8 @@ $CONF['HealthCheckAgent'] = [ 'columns' => [ ['name' => 'healthCheckAgentId', 'read_only' => True, 'type' => 'int', 'protected' => True], - ['name' => 'healthCheckId', 'read_only' => True, 'type' => 'int', 'protected' => True], - ['name' => 'agentId', 'read_only' => True, 'type' => 'int', 'protected' => True], + ['name' => 'healthCheckId', 'read_only' => True, 'type' => 'int', 'protected' => True, 'relation' => 'HealthCheck'], + ['name' => 'agentId', 'read_only' => True, 'type' => 'int', 'protected' => True, 'relation' => 'Agent'], ['name' => 'status', 'read_only' => True, 'type' => 'int', 'protected' => True], ['name' => 'cracked', 'read_only' => True, 'type' => 'int', 'protected' => True], ['name' => 'numGpus', 'read_only' => True, 'type' => 'int', 'protected' => True], @@ -299,7 +299,7 @@ ['name' => 'action', 'read_only' => False, 'type' => 'str(50)'], ['name' => 'objectId', 'read_only' => True, 'type' => 'int', 'protected' => True], ['name' => 'notification', 'read_only' => False, 'type' => 'str(50)'], - ['name' => 'userId', 'read_only' => True, 'type' => 'int', 'protected' => True], + ['name' => 'userId', 'read_only' => True, 'type' => 'int', 'protected' => True, 'relation' => 'User'], ['name' => 'receiver', 'read_only' => False, 'type' => 'str(256)'], ['name' => 'isActive', 'read_only' => False, 'type' => 'bool'], ], @@ -349,7 +349,7 @@ $CONF['Session'] = [ 'columns' => [ ['name' => 'sessionId', 'read_only' => True, 'type' => 'int', 'protected' => True], - ['name' => 'userId', 'read_only' => True, 'type' => 'int', 'protected' => True], + ['name' => 'userId', 'read_only' => True, 'type' => 'int', 'protected' => True, , 'relation' => 'User'], ['name' => 'sessionStartDate', 'read_only' => True, 'type' => 'int64', 'protected' => True], ['name' => 'lastActionDate', 'read_only' => True, 'type' => 'int64', 'protected' => True], ['name' => 'isOpen', 'read_only' => True, 'type' => 'bool', 'protected' => True], @@ -360,8 +360,8 @@ $CONF['Speed'] = [ 'columns' => [ ['name' => 'speedId', 'read_only' => True, 'type' => 'int', 'protected' => True], - ['name' => 'agentId', 'read_only' => True, 'type' => 'int', 'protected' => True], - ['name' => 'taskId', 'read_only' => True, 'type' => 'int', 'protected' => True], + ['name' => 'agentId', 'read_only' => True, 'type' => 'int', 'protected' => True, 'relation' => 'Agent'], + ['name' => 'taskId', 'read_only' => True, 'type' => 'int', 'protected' => True, 'relation' => 'Task'], ['name' => 'speed', 'read_only' => True, 'type' => 'int64', 'protected' => True], ['name' => 'time', 'read_only' => True, 'type' => 'int64', 'protected' => True], ], @@ -394,9 +394,9 @@ ['name' => 'isCpuTask', 'read_only' => False, 'type' => 'bool'], ['name' => 'useNewBench', 'read_only' => True, 'type' => 'bool'], ['name' => 'skipKeyspace', 'read_only' => True, 'type' => 'int64'], - ['name' => 'crackerBinaryId', 'read_only' => True, 'type' => 'int'], - ['name' => 'crackerBinaryTypeId', 'read_only' => True, 'type' => 'int'], - ['name' => 'taskWrapperId', 'read_only' => True, 'type' => 'int', 'protected' => True], + ['name' => 'crackerBinaryId', 'read_only' => True, 'type' => 'int', 'relation' => 'CrackerBinary'], + ['name' => 'crackerBinaryTypeId', 'read_only' => True, 'type' => 'int', 'relation' => 'CrackerBinaryType'], + ['name' => 'taskWrapperId', 'read_only' => True, 'type' => 'int', 'protected' => True, , 'relation' => 'TaskWrapper'], ['name' => 'isArchived', 'read_only' => False, 'type' => 'bool'], ['name' => 'notes', 'read_only' => False, 'type' => 'str(65535)'], ['name' => 'staticChunks', 'read_only' => True, 'type' => 'int'], @@ -409,7 +409,7 @@ $CONF['TaskDebugOutput'] = [ 'columns' => [ ['name' => 'taskDebugOutputId', 'read_only' => True, 'type' => 'int', 'protected' => True], - ['name' => 'taskId', 'read_only' => True, 'type' => 'int'], + ['name' => 'taskId', 'read_only' => True, 'type' => 'int', 'relation' => 'Task'], ['name' => 'output', 'read_only' => True, 'type' => 'str(256)'], ], ]; @@ -419,8 +419,8 @@ ['name' => 'priority', 'read_only' => False, 'type' => 'int'], ['name' => 'maxAgents', 'read_only' => False, 'type' => 'int'], ['name' => 'taskType', 'read_only' => True, 'type' => 'int', 'protected' => True, 'choices' => $FieldTaskTypeChoices], - ['name' => 'hashlistId', 'read_only' => True, 'type' => 'int', 'protected' => True], - ['name' => 'accessGroupId', 'read_only' => False, 'type' => 'int'], + ['name' => 'hashlistId', 'read_only' => True, 'type' => 'int', 'protected' => True, 'relation' => 'Hashlist'], + ['name' => 'accessGroupId', 'read_only' => False, 'type' => 'int', 'relation' => 'AccessGroup'], ['name' => 'taskWrapperName', 'read_only' => False, 'type' => 'str(100)'], ['name' => 'isArchived', 'read_only' => False, 'type' => 'bool'], ['name' => 'cracked', 'read_only' => False, 'type' => 'int', 'protected' => True], @@ -438,7 +438,7 @@ ['name' => 'lastLoginDate', 'read_only' => True, 'type' => 'int64', 'protected' => True], ['name' => 'registeredSince', 'read_only' => True, 'type' => 'int64', 'protected' => True], ['name' => 'sessionLifetime', 'read_only' => True, 'type' => 'int', 'protected' => True], - ['name' => 'rightGroupId', 'read_only' => False, 'type' => 'int', 'alias' => 'globalPermissionGroupId'], + ['name' => 'rightGroupId', 'read_only' => False, 'type' => 'int', 'alias' => 'globalPermissionGroupId', 'relation' => 'RightGroup' ], ['name' => 'yubikey', 'read_only' => True, 'type' => 'str(256)', 'protected' => True], ['name' => 'otp1', 'read_only' => True, 'type' => 'str(256)', 'protected' => True], ['name' => 'otp2', 'read_only' => True, 'type' => 'str(256)', 'protected' => True], @@ -451,8 +451,8 @@ ['name' => 'zapId', 'read_only' => True, 'type' => 'int', 'protected' => True], ['name' => 'hash', 'read_only' => True, 'type' => 'str(65535)', 'protected' => True], ['name' => 'solveTime', 'read_only' => True, 'type' => 'int64', 'protected' => True], - ['name' => 'agentId', 'read_only' => True, 'type' => 'int', 'protected' => True], - ['name' => 'hashlistId', 'read_only' => True, 'type' => 'int', 'protected' => True], + ['name' => 'agentId', 'read_only' => True, 'type' => 'int', 'protected' => True, 'relation' => 'Agent'], + ['name' => 'hashlistId', 'read_only' => True, 'type' => 'int', 'protected' => True, 'relation' => 'Hashlist'], ], ]; // @@ -461,43 +461,43 @@ $CONF['AccessGroupUser'] = [ 'columns' => [ ['name' => 'accessGroupUserId', 'read_only' => True, 'type' => 'int', 'protected' => True], - ['name' => 'accessGroupId', 'read_only' => True, 'type' => 'int'], - ['name' => 'userId', 'read_only' => True, 'type' => 'int'], + ['name' => 'accessGroupId', 'read_only' => True, 'type' => 'int', 'relation' => 'AccessGroup'], + ['name' => 'userId', 'read_only' => True, 'type' => 'int', 'relation' => 'User'], ], ]; $CONF['AccessGroupAgent'] = [ 'columns' => [ ['name' => 'accessGroupAgentId', 'read_only' => True, 'type' => 'int', 'protected' => True], - ['name' => 'accessGroupId', 'read_only' => True, 'type' => 'int'], - ['name' => 'agentId', 'read_only' => True, 'type' => 'int'], + ['name' => 'accessGroupId', 'read_only' => True, 'type' => 'int', 'relation' => 'accessGroup'], + ['name' => 'agentId', 'read_only' => True, 'type' => 'int', 'relation' => 'Agent'], ], ]; $CONF['FileTask'] = [ 'columns' => [ ['name' => 'fileTaskId', 'read_only' => True, 'type' => 'int', 'protected' => True], - ['name' => 'fileId', 'read_only' => True, 'type' => 'int'], - ['name' => 'taskId', 'read_only' => True, 'type' => 'int'], + ['name' => 'fileId', 'read_only' => True, 'type' => 'int', 'relation' => 'File'], + ['name' => 'taskId', 'read_only' => True, 'type' => 'int', 'relation' => 'Task'], ], ]; $CONF['FilePretask'] = [ 'columns' => [ ['name' => 'filePretaskId', 'read_only' => True, 'type' => 'int', 'protected' => True], - ['name' => 'fileId', 'read_only' => True, 'type' => 'int'], - ['name' => 'pretaskId', 'read_only' => True, 'type' => 'int'], + ['name' => 'fileId', 'read_only' => True, 'type' => 'int', 'relation' => 'File'], + ['name' => 'pretaskId', 'read_only' => True, 'type' => 'int', 'relation' => 'PreTask'], ], ]; $CONF['SupertaskPretask'] = [ 'columns' => [ ['name' => 'supertaskPretaskId', 'read_only' => True, 'type' => 'int', 'protected' => True], - ['name' => 'supertaskId', 'read_only' => True, 'type' => 'int'], - ['name' => 'pretaskId', 'read_only' => True, 'type' => 'int'], + ['name' => 'supertaskId', 'read_only' => True, 'type' => 'int', 'relation' => 'Supertask'], + ['name' => 'pretaskId', 'read_only' => True, 'type' => 'int', 'relation' => 'Pretask'], ], ]; $CONF['HashlistHashlist'] = [ 'columns' => [ ['name' => 'hashlistHashlistId', 'read_only' => True, 'type' => 'int', 'protected' => True], - ['name' => 'parentHashlistId', 'read_only' => True, 'type' => 'int'], - ['name' => 'hashlistId', 'read_only' => True, 'type' => 'int'], + ['name' => 'parentHashlistId', 'read_only' => True, 'type' => 'int', 'relation' => 'Hashlist'], + ['name' => 'hashlistId', 'read_only' => True, 'type' => 'int', 'relation' => 'Hashlist'], ], ]; diff --git a/src/inc/apiv2/common/AbstractBaseAPI.class.php b/src/inc/apiv2/common/AbstractBaseAPI.class.php index c5018ace3..1a15e1df0 100644 --- a/src/inc/apiv2/common/AbstractBaseAPI.class.php +++ b/src/inc/apiv2/common/AbstractBaseAPI.class.php @@ -1,11 +1,15 @@ user; } - /** - * Available 'expand' parameters on $object - */ - public function getExpandables(): array { - return []; - } - - /** - * Fetch objects for $expand on $objects - */ - protected function fetchExpandObjects(array $objects, string $expand): mixed { - } - protected static function getModelFactory(string $model): object { switch($model) { case AccessGroup::class: return Factory::getAccessGroupFactory(); + case AccessGroupAgent::class: + return Factory::getAccessGroupAgentFactory(); + case AccessGroupUser::class: + return Factory::getAccessGroupUserFactory(); case Agent::class: return Factory::getAgentFactory(); case AgentBinary::class: @@ -173,6 +172,10 @@ protected static function getModelFactory(string $model): object { return Factory::getCrackerBinaryTypeFactory(); case File::class: return Factory::getFileFactory(); + case FileTask::class: + return Factory::getFileTaskFactory(); + case FilePretask::class: + return Factory::getFilePretaskFactory(); case Hash::class: return Factory::getHashFactory(); case Hashlist::class: @@ -201,6 +204,8 @@ protected static function getModelFactory(string $model): object { return Factory::getSpeedFactory(); case Supertask::class: return Factory::getSupertaskFactory(); + case SupertaskPretask::class: + return Factory::getSupertaskPretaskFactory(); case Task::class: return Factory::getTaskFactory(); case TaskWrapper::class: @@ -261,9 +266,29 @@ final protected static function getUser(int $pk): User return self::fetchOne(User::class, $pk); } + /** + * Return Object Resource Type Identifier of API object. + * + * @param mixed $obj + * @return string + */ + final protected function getObjectTypeName($obj): string + { + + $container = $this->container->get('classMapper'); + + if (is_string($obj)) { + $apiClass = $this->container->get('classMapper')->get($obj); + } else { + $apiClass = $this->container->get('classMapper')->get(get_class($obj)); + } + + /* Use the API class Name as type identifier written in camelCase*/ + return lcfirst(substr($apiClass, 0, -3)); + } /** - * Retrieve permissions based on expand section + * Retrieve permissions based on expand section */ protected static function getExpandPermissions(string $expand): array { @@ -271,7 +296,7 @@ protected static function getExpandPermissions(string $expand): array 'assignedAgents' => [Agent::PERM_READ], 'agent' => [Agent::PERM_READ], 'agents' => [AccessGroup::PERM_READ], - 'agentstats' => [AgentStat::PERM_READ], + 'agentStats' => [AgentStat::PERM_READ], 'accessGroups' => [AccessGroup::PERM_READ], 'accessGroup' => [AccessGroup::PERM_READ], 'chunk' => [Chunk::PERM_READ], @@ -293,6 +318,7 @@ protected static function getExpandPermissions(string $expand): array 'files' => [FileTask::PERM_READ, File::PERM_READ], 'pretasks' => [Supertask::PERM_READ, Pretask::PERM_READ], 'user' => [User::PERM_READ], + 'users' => [User::PERM_READ], 'userMembers' => [User::PERM_READ], 'agentMembers' => [Agent::PERM_READ], ); @@ -396,8 +422,6 @@ protected static function db2json(array $feature, mixed $val): mixed } } elseif ($feature['type'] == 'array' && $feature['subtype'] == 'int') { $obj = array_map('intval', preg_split("/,/", $val, -1, PREG_SPLIT_NO_EMPTY)); - } elseif ($feature['type'] == 'dict' && $feature['subtype'] = 'bool') { - $obj = unserialize($val); } elseif (str_starts_with($feature['type'], 'str') && $val !== null) { $obj = html_entity_decode($val, ENT_COMPAT, "UTF-8"); } @@ -457,6 +481,110 @@ protected function obj2Array(object $obj) return $item; } + /** + * Convert DB object JSON:API Resource Object + */ + protected function obj2Resource(object $obj, array $expandResult = []) + { + // Convert values to JSON supported types + $features = $obj->getFeatures(); + $kv = $obj->getKeyValueDict(); + + $apiClass = $this->container->get('classMapper')->get(get_class($obj)); + $linkSelf = $this->routeParser->urlFor($apiClass . ':getOne', ['id' => $obj->getId()]); + + $attributes = []; + $relationships = []; + + /* Collect attributes */ + foreach ($features as $name => $feature) { + // If a attribute is set to private, it should be hidden and not returned. + // Example of this is the password hash. + if ($feature['private'] === true) { + continue; + } + // Hide the primaryKey from the attributes since this is used as indentifier (id) in response + if ($feature['pk'] === true) { + continue; + } + $attributes[$feature['alias']] = $apiClass::db2json($feature, $kv[$name]); + } + + + /* Build JSON::API relationship resource */ + $toManyRelationships = $apiClass::getToManyRelationships(); + $toOneRelationships = $apiClass::getToOneRelationships(); + + $relationshipsNames = array_merge(array_keys($toOneRelationships), array_keys($toManyRelationships)); + sort($relationshipsNames); + foreach ($relationshipsNames as $relationshipName) { + $relationships[$relationshipName] = [ + "links" => [ + "self" => $linkSelf . "/relationships/" . $relationshipName, + "related" => $linkSelf . "/" . $relationshipName, + ] + ]; + } + + /* Generate to-many relationships entries */ + foreach ($toManyRelationships as $relationshipName => $toManyRelationship) { + // Build (optional) compound document resource linkage + if (array_key_exists($relationshipName, $expandResult)) { + $relationships[$relationshipName]["data"] = []; + + // Empty to-many relationship + if (array_key_exists($obj->getId(), $expandResult[$relationshipName]) === false) { + continue; + } + + // Fetch to-many-objects + $expandObjects = $expandResult[$relationshipName][$obj->getId()]; + foreach($expandObjects as $relationObject) { + $relationships[$relationshipName]["data"][] = [ + "type" => $this->getObjectTypeName($relationObject), + "id" => $relationObject->getId() + ]; + } + } + } + + /* Generate to-one relationships entries */ + foreach ($toOneRelationships as $relationshipName => $toOneRelationship) { + // Build (optional) compound document resource linkage + if (array_key_exists($relationshipName, $expandResult)) { + // Empty to-one relationship + if (array_key_exists($obj->getId(), $expandResult[$relationshipName]) === false) { + $relationships[$relationshipName]["data"] = null; + continue; + } + + // Fetch to-one-objects + $expandObject = $expandResult[$relationshipName][$obj->getId()]; + + $relationships[$relationshipName]["data"] = [ + "type" => $this->getObjectTypeName($expandObject), + "id" => $expandObject->getId() + ]; + } + } + + + $newObject = [ + "type" => $this->getObjectTypeName($obj), + "id" => $obj->getId(), + "attributes" => $attributes, + "links" => [ + "self" => $linkSelf, + ], + ]; + + if (sizeof($relationships) > 0) { + $newObject['relationships'] = $relationships; + } + + return $newObject; + } + /** * Quirck to resolve objects via ManyToMany relation table */ @@ -531,7 +659,7 @@ protected function object2Array(object $object, array $expands = []): array /** * Uniform conversion of php array to JSON output */ - protected function ret2json(array $result): string + protected static function ret2json(array $result): string { return json_encode($result, JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT | JSON_THROW_ON_ERROR) . PHP_EOL; } @@ -556,6 +684,37 @@ protected function unaliasData(array $data, array $features): array { return $mappedData; } + /** + * Validate the Permission of a DBA column and check if it key may be altered + * + * @param Request $request Current request that is being handled + * @param string $key Field to use as base for $objects + * @param array $features The features of the DBA object of the child + * + * @throws HttpForbiddenException when it is not allowed to alter the key + * + * @return void + */ + protected function isAllowedToMutate(Request $request, array $features, string $key) { + if (is_string($key) == False) { + throw new HttpErrorException("Key '$key' invalid", 403); + } + // Ensure key exists in target array + if (array_key_exists($key, $features) == False) { + throw new HttpErrorException("Key '$key' does not exists!", 403); + } + + if ($features[$key]['read_only'] == True) { + throw new HttpForbiddenException($request, "Key '$key' is immutable"); + } + if ($features[$key]['protected'] == True) { + throw new HttpForbiddenException($request, "Key '$key' is protected"); + } + if ($features[$key]['private'] == True) { + throw new HttpForbiddenException($request, "Key '$key' is private"); + } + } + /** * Validate incoming data */ @@ -581,15 +740,24 @@ protected function validateData(array $data, array $features) } // Int } elseif (str_starts_with($features[$key]['type'], 'int')) { - // TODO: int32, int64 range validation if (is_integer($value) == False) { throw new HttpErrorException("Key '$key' is not of type integer"); } + $maxValue = ($features[$key]['type'] === 'int64') ? 9223372036854775807 : 2147483647; + if ($value > $maxValue || $value < -$maxValue) { + throw new HttpErrorException("The value exceeds the limit for a {$features[$key]['type']} integer."); + } // Str } elseif (str_starts_with($features[$key]['type'], 'str')) { if (is_string($value) == False) { throw new HttpErrorException("Key '$key' is not of type string"); } + if (preg_match('/str\((\d+)\)/', $features[$key]['type'], $matches)) { + $max_string_len = (int) $matches[1]; + if (strlen($value) > $max_string_len) { + throw new HttpErrorException("The string value: '$value' is too long. The max size is '$max_string_len'"); + } + } // TODO: Length validation // Array } elseif (str_starts_with($features[$key]['type'], 'array')) { @@ -629,7 +797,6 @@ protected function validateData(array $data, array $features) } } - /** * Validate incoming parameter keys */ @@ -654,7 +821,7 @@ protected function validateParameters(array $data, array $allFeatures): void { ksort($invalidKeys); ksort($validFeatures); throw new HTException("Parameter(s) '" . join(", ", $invalidKeys) . "' not valid input " . - "(valid key(s) : '" . join(", ", $validFeatures) . ")'"); + "(valid key(s) : '" . join(", ", $validFeatures) . ")'", 403); } // Find out about mandatory parameters which are not provided @@ -666,30 +833,17 @@ protected function validateParameters(array $data, array $allFeatures): void { } } - - /** * Check for valid expand parameters. */ + //TODO: nice to have would be to be able to include objects that are further away in the relationship + //ex. from Hash include=hashlist.task to include all tasks from a hash (section 8.3 JSON API) protected function makeExpandables(Request $request, array $validExpandables): array { $data = $request->getParsedBody(); + $queryExpands = (array_key_exists('include', $request->getQueryParams())) ? preg_split("/[,\ ]+/", $request->getQueryParams()['include']) : []; - // Body expand can be specified as single item or array of items - $bodyExpands = []; - if (!is_null($data) and array_key_exists('expand', $data)) { - if (is_array($data['expand'])) { - array_push($bodyExpands, ...$data['expand']); - } else if (is_string($data['expand'])) { - array_push($bodyExpands, ...preg_split("/[,\ ]+/", $data['expand'])); - } else { - assert(False, "Parameter expand type: '" . gettype($data['expand']) . "' not allowed"); - } - } - $queryExpands = (array_key_exists('expand', $request->getQueryParams())) ? preg_split("/[,\ ]+/", $request->getQueryParams()['expand']) : []; - - $mergedExpands = array_merge($bodyExpands, $queryExpands); - foreach ($mergedExpands as $expand) { + foreach ($queryExpands as $expand) { if (in_array($expand, $validExpandables) == false) { throw new HTException("Parameter '" . $expand . "' is not valid expand key (valid keys are: " . join(", ", array_values($validExpandables)) . ")"); } @@ -697,14 +851,14 @@ protected function makeExpandables(Request $request, array $validExpandables): a /* Validate expand parameters for required permissions */ $required_perms = []; - foreach ($mergedExpands as $expand) { + foreach ($queryExpands as $expand) { array_push($required_perms, ...self::getExpandPermissions($expand)); } if ($this->validatePermissions($required_perms) === FALSE) { throw new HttpForbiddenException($request, 'Permissions missing on expand parameter objects! || ' . join('||', $this->permissionErrors)); } - return $mergedExpands; + return $queryExpands; } /** @@ -721,20 +875,6 @@ protected function getPrimaryKey(): string } } - private function getFilterParameters(Request $request, string $key): array { - $data = $request->getParsedBody(); - if (!is_null(($data))) { - $bodyFilter = (array_key_exists($key, $data)) ? $data[$key] : []; - } else { - $bodyFilter = []; - } - - $queryFilter = (array_key_exists($key, $request->getQueryParams())) ? preg_split("/[,\ ]+/", $request->getQueryParams()[$key]) : []; - $mergedFilters = array_merge($bodyFilter, $queryFilter); - - return $mergedFilters; - } - /** * Check for valid filter parameters and build QueryFilter */ @@ -742,16 +882,16 @@ protected function makeFilter(Request $request, array $features): array { $qFs = []; - $mergedFilters = $this->getFilterParameters($request, 'filter'); - foreach ($mergedFilters as $filter) { - // TODO: Add sanity checking - if (preg_match('/^(?P[_a-zA-Z0-9]+?)(?=|__eq=|!=|__ne=|>|__lt=|>=|__lte=|<|__gt=|<=|__gte=|__contains=|__startswith=|__endswith=|__icontains=|__istartswith=|__iendswith=)(?P[^=]+)$/', $filter, $matches) == 0) { + $filters = $this->getQueryParameterFamily($request, 'filter'); + foreach ($filters as $filter => $value) { + + if (preg_match('/^(?P[_a-zA-Z0-9]+?)(?|__eq|__ne|__lt|__lte|__gt|__gte|__contains|__startswith|__endswith|__icontains|__istartswith|__iendswith)$/', $filter, $matches) == 0) { throw new HTException("Filter parameter '" . $filter . "' is not valid"); } // Special filtering of _id to use for uniform access to model primary key - $cast_key = $matches['key'] == '_id' ? $this->getPrimaryKey() : $matches['key']; - + $cast_key = $matches['key'] == '_id' ? array_column($features, 'alias', 'dbname')[$this->getPrimaryKey()] : $matches['key']; + if (array_key_exists($cast_key, $features) == false) { throw new HTException("Filter parameter '" . $filter . "' is not valid (key not valid field)"); }; @@ -759,64 +899,59 @@ protected function makeFilter(Request $request, array $features): array // TODO Merge/Combine with validate parameters switch($features[$cast_key]['type']) { case 'bool': - $val = filter_var($matches['value'], FILTER_VALIDATE_BOOLEAN, FILTER_NULL_ON_FAILURE); + $val = filter_var($value, FILTER_VALIDATE_BOOLEAN, FILTER_NULL_ON_FAILURE); if (is_null($val)) { throw new HTException("Filter parameter '" . $filter . "' is not valid boolean value"); } break; case 'int': - $val = filter_var($matches['value'], FILTER_VALIDATE_INT, FILTER_NULL_ON_FAILURE); + $val = filter_var($value, FILTER_VALIDATE_INT, FILTER_NULL_ON_FAILURE); if (is_null($val)) { throw new HTException("Filter parameter '" . $filter . "' is not valid integer value"); } default: - $val = $matches['value']; + $val = $value; } // We need to remap any aliased key to the key as it appears in the database. $remappedKey = $features[$cast_key]['dbname']; switch($matches['operator']) { - case '=': - case '__eq=': + case '': + case '__eq': array_push($qFs, new QueryFilter($remappedKey, $val, '=')); break; - case '!=': - case '__ne=': + case '__ne': array_push($qFs, new QueryFilter($remappedKey, $val, '!=')); break; - case '<': - case '__lt=': + case '__lt': array_push($qFs, new QueryFilter($remappedKey, $val, '<')); break; - case '<=': - case '__lte=': + case '__lte': array_push($qFs, new QueryFilter($remappedKey, $val, '<=')); break; - case '>': - case '__gt=': + case '__gt': array_push($qFs, new QueryFilter($remappedKey, $val, '>')); break; - case '>=': - case '__gte=': + case '__gte': array_push($qFs, new QueryFilter($remappedKey, $val, '>=')); break; - case '__contains=': + case '__contains': array_push($qFs, new LikeFilter($remappedKey, "%" . $val . "%")); break; - case '__startswith=': + case '__startswith': array_push($qFs, new LikeFilter($remappedKey, $val . "%")); break; - case '__endswith=': + case '__endswith': array_push($qFs, new LikeFilter($remappedKey, "%" . $val)); break; - case '__icontains=': + case '__icontains': array_push($qFs, new LikeFilterInsensitive($remappedKey, "%" . $val . "%")); break; - case '__istartswith=': + case '__istartswith': array_push($qFs, new LikeFilterInsensitive($remappedKey, $val . "%")); break; - case '__iendswith=': + case '__iendswith': array_push($qFs, new LikeFilterInsensitive($remappedKey, "%" . $val)); break; default: @@ -830,18 +965,22 @@ protected function makeFilter(Request $request, array $features): array /** * Check for valid ordering parameters and build QueryFilter */ - protected function makeOrderFilter(Request $request, array $features): array + protected function makeOrderFilterTemplates(Request $request, array $features, $defaultSort = 'ASC'): array { - $oFs = []; + $orderTemplates = []; - $mergedOrdering = $this->getFilterParameters($request, 'ordering'); - foreach ($mergedOrdering as $order) { + $orderings = $this->getQueryParameterAsList($request, 'sort'); + $contains_primary_key = false; + foreach ($orderings as $order) { if (preg_match('/^(?P[-])?(?P[_a-zA-Z]+)$/', $order, $matches)) { // Special filtering of _id to use for uniform access to model primary key $cast_key = $matches['key'] == '_id' ? $this->getPrimaryKey() : $matches['key']; + if ($cast_key == $this->getPrimaryKey()) { + $contains_primary_key = true; + } if (array_key_exists($cast_key, $features)) { $remappedKey = $features[$cast_key]['dbname']; - $oFs[] = new OrderFilter($remappedKey, ($matches['operator'] == '-') ? "DESC" : "ASC"); + array_push($orderTemplates, ['by' => $remappedKey, 'type' => ($matches['operator'] == '-') ? "DESC" : "ASC" ]); } else { throw new HTException("Ordering parameter '" . $order . "' is not valid"); } @@ -850,9 +989,14 @@ protected function makeOrderFilter(Request $request, array $features): array } } - return $oFs; - } + //when no primary key has been added in the sort parameter, add the default case of sorting on primary key as last sort + if ($contains_primary_key == false) { + array_push($orderTemplates, ['by' =>$this->getPrimaryKey(), 'type' => $defaultSort]); + } + return $orderTemplates; + } + /** * Validate if user is allowed to access hashlist @@ -973,6 +1117,153 @@ protected function getParam(Request $request, string $param, int $default): int } } + + protected function getQueryParameterAsList(Request $request, string $name): array + { + $queryParams = $request->getQueryParams(); + if (is_array($queryParams) && array_key_exists($name, $queryParams)) { + return preg_split("/[,\ ]+/", $queryParams[$name]); + } else { + return []; + } + } + + + /* + * Return requested parameter, prioritize query parameter over inline payload parameter + */ + protected function getQueryParameterFamilyMember(Request $request, string $family, string $member): string|null + { + $queryParams = $request->getQueryParams(); + // Check query parameters and make sure it is an array + if (is_array($queryParams) && array_key_exists($family, $queryParams) && array_key_exists($member, $queryParams[$family])) { + return $queryParams[$family][$member]; + } + + return null; + } + + + /* + * Return requested parameter, prioritize query parameter over inline payload parameter + */ + protected function getQueryParameterFamily(Request $request, string $family): array + { + $retval = []; + $queryParams = $request->getQueryParams(); + if (array_key_exists($family, $queryParams) and is_array($queryParams[$family])) { + // TODO: Enhance validation + return $queryParams[$family]; + } + + return $retval; + } + + static function createJsonResponse(array $data = [], array $links = [], array $included = [], array $meta = []) { + $response = [ + "jsonapi" => [ + "version" => "1.1", + "ext" => [ + "https://jsonapi.org/profiles/ethanresnick/cursor-pagination" + ], + ], + ]; + + if (!empty($links)) { + $response["links"] = $links; + } + + if(!empty($meta)) { + $response["meta"] = $meta; + } + + $response["data"] = $data; + + if (!empty($included)) { + $response["included"] = $included; + } + + return $response; +} + + /** + * Get single Resource + */ + protected static function getOneResource(object $apiClass, object $object, Request $request, Response $response, int $statusCode=200): Response + { + $apiClass->preCommon($request); + + $validExpandables = $apiClass->getExpandables(); + $expands = $apiClass->makeExpandables($request, $validExpandables); + + $objects = [$object]; + + /* Resolve all expandables */ + $expandResult = []; + foreach ($expands as $expand) { + // mapping from $objectId -> result objects in + $expandResult[$expand] = $apiClass->fetchExpandObjects($objects, $expand); + } + + /* Convert objects to JSON:API */ + $dataResources = []; + $includedResources = []; + + // Convert objects to data resources + foreach ($objects as $object) { + // Create object + $newObject = $apiClass->obj2Resource($object, $expandResult); + + // For compound document, included resources + foreach ($expands as $expand) { + if (array_key_exists($object->getId(), $expandResult[$expand])) { + $expandResultObject = $expandResult[$expand][$object->getId()]; + if (is_array($expandResultObject)) { + foreach($expandResultObject as $expandObject) { + $includedResources[] = $apiClass->obj2Resource($expandObject); + } + } else { + if ($expandResultObject === null) { + // to-only relation which is nullable + continue; + } + $includedResources[] = $apiClass->obj2Resource($expandResultObject); + } + } + } + + // Add to result output + $dataResources[] = $newObject; + } + + $selfParams = $request->getQueryParams(); + $linksQuery = urldecode(http_build_query($selfParams)); + + $linksSelf = $request->getUri()->getPath() . ((!empty($linksQuery)) ? '?' . $linksQuery : ''); + $links = ["self" => $linksSelf]; + + // Generate JSON:API GET output + $ret = self::createJsonResponse($dataResources[0], $links, $includedResources); + + $body = $response->getBody(); + $body->write($apiClass->ret2json($ret)); + + return $response->withStatus($statusCode) + ->withHeader("Content-Type", "application/vnd.api+json") + ->withHeader("Location", $dataResources[0]["links"]["self"]); + //for location we use links value from $dataresources because if we use $linksSelf, the wrong location gets returned in + //case of a POST request + } + + //Meta response for helper functions that do not respond with resource records + protected static function getMetaResponse(array $meta, Request $request, Response $response, int $statusCode=200) { + $ret = self::createJsonResponse($meta=$meta); + $body = $response->getBody(); + $body->write(self::ret2json($ret)); + + return $response->withStatus($statusCode)->withHeader("Content-Type", "application/vnd.api+json"); + } + /** * Override-able activated methods */ diff --git a/src/inc/apiv2/common/AbstractHelperAPI.class.php b/src/inc/apiv2/common/AbstractHelperAPI.class.php index 62f4fa0fe..ce57cc37e 100644 --- a/src/inc/apiv2/common/AbstractHelperAPI.class.php +++ b/src/inc/apiv2/common/AbstractHelperAPI.class.php @@ -17,8 +17,9 @@ use DBA\User; abstract class AbstractHelperAPI extends AbstractBaseAPI { - abstract public function actionPost(array $data): array|null; + abstract public function actionPost(array $data): object|array|null; + /* Chunk API endpoint specific call to abort chunk */ public function processPost(Request $request, Response $response, array $args): Response { @@ -35,28 +36,22 @@ public function processPost(Request $request, Response $response, array $args): $this->validateData($data, $allFeatures); /* All creation of object */ - try { - // TODO: Validate data is compliant with https://jsonapi.org/format/#document-top-level 'Primary data' - $returnData = $this->actionPost($data); - $status = ($returnData) ? 200 : 204; - $retval['data'] = $returnData; - } catch (Error | Exception $e) { - // https://jsonapi.org/format/#error-objects - $status = 400; - $retval['errors'] = [ - 'status' => $e->getCode(), - 'source' => $e->getFile() . ':' . $e->getLine(), - 'title' => $e->getMessage(), - ]; - } finally { - if ($status == 204) { - return $response->withStatus($status); - } else { - $response->getBody()->write($this->ret2json($retval)); - return $response->withStatus($status) - ->withHeader("Content-Type", "application/json"); - } - } + $newObject = $this->actionPost($data); + + /* Successfully executed action of type update/delete */ + if ($newObject == null) { + return $response->withStatus(204); + } + + + /* Succesful executed action of create */ + if (is_object($newObject)) { + $apiClass = new ($this->container->get('classMapper')->get($newObject::class))($this->container); + return self::getOneResource($apiClass, $newObject, $request, $response); + /* A meta response of a helper function */ + } elseif (is_array($newObject)) { + return self::getMetaResponse($newObject, $request, $response); + } } /** diff --git a/src/inc/apiv2/common/AbstractModelAPI.class.php b/src/inc/apiv2/common/AbstractModelAPI.class.php index a9a5bad15..9a07c6ec9 100644 --- a/src/inc/apiv2/common/AbstractModelAPI.class.php +++ b/src/inc/apiv2/common/AbstractModelAPI.class.php @@ -5,31 +5,105 @@ use Psr\Http\Message\ServerRequestInterface as Request; use Slim\Exception\HttpNotFoundException; - +use Slim\Exception\HttpForbiddenException; +use Middlewares\Utils\HttpErrorException; use DBA\AbstractModelFactory; use DBA\JoinFilter; use DBA\Factory; use DBA\ContainFilter; +use DBA\LimitFilter; use DBA\OrderFilter; use DBA\QueryFilter; -use Middlewares\Utils\HttpErrorException; +use Psr\Http\Message\ServerRequestInterface; - -abstract class AbstractModelAPI extends AbstractBaseAPI { - abstract static public function getDBAClass(): string; +abstract class AbstractModelAPI extends AbstractBaseAPI +{ + abstract static public function getDBAClass(); abstract protected function createObject(array $data): int; abstract protected function deleteObject(object $object): void; - protected function getFactory(): object { - return self::getModelFactory($this->getDBAclass()); + public static function getToOneRelationships(): array + { + return []; + } + public static function getToManyRelationships(): array + { + return []; + } + + + /** + * Available 'expand' parameters on $object + */ + public static function getExpandables(): array + { + $expandables = array_merge(array_keys(static::getToOneRelationships()), array_keys(static::getToManyRelationships())); + return $expandables; + } + + // /** + // * Fetch objects for $expand on $objects + // */ + protected static function fetchExpandObjects(array $objects, string $expand): mixed + { + //disabled the check because with intermediate objects its possible to fetch a different model + /* Ensure we receive the proper type */ + // $baseModel = static::getDBAClass(); + // array_walk($objects, function ($obj) use ($baseModel) { + // assert($obj instanceof $baseModel); + // }); + + $toOneRelationships = static::getToOneRelationships(); + if (array_key_exists($expand, $toOneRelationships)) { + $relationFactory = self::getModelFactory($toOneRelationships[$expand]['relationType']); + return self::getForeignKeyRelation( + $objects, + $toOneRelationships[$expand]['key'], + $relationFactory, + $toOneRelationships[$expand]['relationKey'], + ); + }; + + $toManyRelationships = static::getToManyRelationships(); + if (array_key_exists($expand, $toManyRelationships)) { + $relationFactory = self::getModelFactory($toManyRelationships[$expand]['relationType']); + + /* Associative entity */ + if (array_key_exists('junctionTableType', $toManyRelationships[$expand])) { + $junctionTableFactory = self::getModelFactory($toManyRelationships[$expand]['junctionTableType']); + return self::getManyToOneRelationViaIntermediate( + $objects, + $toManyRelationships[$expand]['key'], + $junctionTableFactory, + $toManyRelationships[$expand]['junctionTableFilterField'], + $relationFactory, + $toManyRelationships[$expand]['relationKey'], + ); + }; + + return self::getManyToOneRelation( + $objects, + $toManyRelationships[$expand]['key'], + $relationFactory, + $toManyRelationships[$expand]['relationKey'], + ); + }; + + throw new BadFunctionCallException("Internal error: Expansion '$expand' not implemented!"); + } + + + final protected static function getFactory(): object + { + return self::getModelFactory(static::getDBAclass()); } /** * Get features based on Formfields and DBA model features */ final protected function getFeatures(): array - { + { return array_merge( parent::getFeatures(), call_user_func($this->getDBAclass() . '::getFeatures'), @@ -37,140 +111,168 @@ final protected function getFeatures(): array } /** - * Retrieve ForeignKey Relation - * - * @param array $objects Objects Fetch relation for selected Objects - * @param string $objectField Field to use as base for $objects - * @param object $factory Factory used to retrieve objects - * @param string $filterField Filter field of $field to filter against $objects field - * - * @return array - */ - final protected static function getForeignKeyRelation( - array $objects, - string $objectField, - object $factory, - string $filterField - ): array { - assert($factory instanceof AbstractModelFactory); - $retval = array(); - - /* Fetch required objects */ - $objectIds = []; - foreach($objects as $object) { - $kv = $object->getKeyValueDict(); - $objectIds[] = $kv[$objectField]; - } - $qF = new ContainFilter($filterField, $objectIds, $factory); - $hO = $factory->filter([Factory::FILTER => $qF]); - - /* Objects are uniquely identified by fields, create mapping to speed-up further processing */ - $f2o = []; - foreach ($hO as $relationObject) { - $f2o[$relationObject->getKeyValueDict()[$filterField]] = $relationObject; - }; + * Get features based on DBA model features + * + * @param string $dbaClass is the dba class to get the features from + */ + //TODO doesnt retrieve features based on formfields, could be done by adding api class in relationship objects + final protected function getFeaturesOther(string $dbaClass): array + { + return call_user_func($dbaClass . '::getFeatures'); + } - /* Map objects */ - foreach ($objects as $object) { - $fieldId = $object->getKeyValueDict()[$objectField]; - if (array_key_exists($fieldId, $f2o) == true) { - $retval[$object->getId()] = $f2o[$fieldId]; - } + /** + * Find primary key for another DBA object + * A little bit hacky because the getPrimaryKey function in dbaClass is not static + * + * @param string $dbaClass is the dba class to get the primarykey from + */ + protected function getPrimaryKeyOther(string $dbaClass): string + { + $features = $this->getFeaturesOther($dbaClass); + # Work-around required since getPrimaryKey is not static in dba/models/*.php + foreach ($features as $key => $value) { + if ($value['pk'] == True) { + return $key; } + } + } - return $retval; + /** + * Retrieve ForeignKey Relation + * + * @param array $objects Objects Fetch relation for selected Objects + * @param string $objectField Field to use as base for $objects + * @param object $factory Factory used to retrieve objects + * @param string $filterField Filter field of $field to filter against $objects field + * + * @return array + */ + final protected static function getForeignKeyRelation( + array $objects, + string $objectField, + object $factory, + string $filterField + ): array { + assert($factory instanceof AbstractModelFactory); + $retval = array(); + + /* Fetch required objects */ + $objectIds = []; + foreach ($objects as $object) { + $kv = $object->getKeyValueDict(); + $objectIds[] = $kv[$objectField]; } + $qF = new ContainFilter($filterField, $objectIds, $factory); + $hO = $factory->filter([Factory::FILTER => $qF]); - /** - * Retrieve ManyToOneRelation (reverse ForeignKey) - * - * @param array $objects Objects Fetch relation for selected Objects - * @param string $objectField Field to use as base for $objects - * @param object $factory Factory used to retrieve objects - * @param string $filterField Filter field of $field to filter against $objects field - * - * @return array - */ - final protected static function getManyToOneRelation( - array $objects, - string $objectField, - object $factory, - string $filterField - ): array { - assert($factory instanceof AbstractModelFactory); - $retval = array(); - - /* Fetch required objects */ - $objectIds = []; - foreach($objects as $object) { - $kv = $object->getKeyValueDict(); - $objectIds[] = $kv[$objectField]; - } - $qF = new ContainFilter($filterField, $objectIds, $factory); - $hO = $factory->filter([Factory::FILTER => $qF]); + /* Objects are uniquely identified by fields, create mapping to speed-up further processing */ + $f2o = []; + foreach ($hO as $relationObject) { + $f2o[$relationObject->getKeyValueDict()[$filterField]] = $relationObject; + }; - /* Map (multiple) objects to base objects */ - foreach ($hO as $relationObject) { - $kv = $relationObject->getKeyValueDict(); - $retval[$kv[$filterField]][] = $relationObject; + /* Map objects */ + foreach ($objects as $object) { + $fieldId = $object->getKeyValueDict()[$objectField]; + if (array_key_exists($fieldId, $f2o) == true) { + $retval[$object->getId()] = $f2o[$fieldId]; } + } + + return $retval; + } - return $retval; + /** + * Retrieve ManyToOneRelation (reverse ForeignKey) + * + * @param array $objects Objects Fetch relation for selected Objects + * @param string $objectField Field to use as base for $objects + * @param object $factory Factory used to retrieve objects + * @param string $filterField Filter field of $field to filter against $objects field + * + * @return array + */ + final protected static function getManyToOneRelation( + array $objects, + string $objectField, + object $factory, + string $filterField + ): array { + assert($factory instanceof AbstractModelFactory); + $retval = array(); + + /* Fetch required objects */ + $objectIds = []; + foreach ($objects as $object) { + $kv = $object->getKeyValueDict(); + $objectIds[] = $kv[$objectField]; } - + $qF = new ContainFilter($filterField, $objectIds, $factory); + $hO = $factory->filter([Factory::FILTER => $qF]); - /** - * Retrieve ManyToOne relalation for $objects ('parents') of type $targetFactory via 'intermidate' - * of $intermediateFactory joining on $joinField (between 'intermediate' and 'target'). Filtered by - * $filterField at $intermediateFactory. - * - * @param array $objects Objects Fetch relation for selected Objects - * @param string $objectField Field to use as base for $objects - * @param object $intermediateFactory Factory used as intermediate between parentObject and targetObject - * @param string $filterField Filter field of intermadiateObject to filter against $objects field - * @param object $targetFactory Object properties of objects returned - * @param string $joinField Field to connect 'intermediate' to 'target' - - * @return array - */ - final protected static function getManyToOneRelationViaIntermediate( - array $objects, - string $objectField, - object $intermediateFactory, - string $filterField, - object $targetFactory, - string $joinField, - ): array { - assert($intermediateFactory instanceof AbstractModelFactory); - assert($targetFactory instanceof AbstractModelFactory); - $retval = array(); - - - /* Retrieve Parent -> Intermediate -> Target objects */ - $objectIds = []; - foreach($objects as $object) { - $kv = $object->getKeyValueDict(); - $objectIds[] = $kv[$objectField]; - } - $qF = new ContainFilter($filterField, $objectIds, $intermediateFactory); - $jF = new JoinFilter($intermediateFactory, $joinField, $joinField); - $hO = $targetFactory->filter([Factory::FILTER => $qF, Factory::JOIN => $jF]); - - /* Build mapping Parent -> Intermediate */ - $i2p = []; - foreach($hO[$intermediateFactory->getModelName()] as $intermidiateObject) { - $kv = $intermidiateObject->getKeyValueDict(); - $i2p[$kv[$joinField]] = $kv[$filterField]; - } + /* Map (multiple) objects to base objects */ + foreach ($hO as $relationObject) { + $kv = $relationObject->getKeyValueDict(); + $retval[$kv[$filterField]][] = $relationObject; + } + + return $retval; + } - /* Associate Target -> Parent (via Intermediate) */ - foreach($hO[$targetFactory->getModelName()] as $targetObject) { - $parent = $i2p[$targetObject->getKeyValueDict()[$joinField]]; - $retval[$parent][] = $targetObject; - } - return $retval; + /** + * Retrieve ManyToOne relalation for $objects ('parents') of type $targetFactory via 'intermidate' + * of $intermediateFactory joining on $joinField (between 'intermediate' and 'target'). Filtered by + * $filterField at $intermediateFactory. + * + * @param array $objects Objects Fetch relation for selected Objects + * @param string $objectField Field to use as base for $objects + * @param object $intermediateFactory Factory used as intermediate between parentObject and targetObject + * @param string $filterField Filter field of intermadiateObject to filter against $objects field + * @param object $targetFactory Object properties of objects returned + * @param string $joinField Field to connect 'intermediate' to 'target' + + * @return array + */ + final protected static function getManyToOneRelationViaIntermediate( + array $objects, + string $objectField, + object $intermediateFactory, + string $filterField, + object $targetFactory, + string $joinField, + ): array { + assert($intermediateFactory instanceof AbstractModelFactory); + assert($targetFactory instanceof AbstractModelFactory); + $retval = array(); + + + /* Retrieve Parent -> Intermediate -> Target objects */ + $objectIds = []; + foreach ($objects as $object) { + $kv = $object->getKeyValueDict(); + $objectIds[] = $kv[$objectField]; } + $qF = new ContainFilter($filterField, $objectIds, $intermediateFactory); + $jF = new JoinFilter($intermediateFactory, $joinField, $joinField); + $hO = $targetFactory->filter([Factory::FILTER => $qF, Factory::JOIN => $jF]); + + /* Build mapping Parent -> Intermediate */ + $i2p = []; + foreach ($hO[$intermediateFactory->getModelName()] as $intermidiateObject) { + $kv = $intermidiateObject->getKeyValueDict(); + $i2p[$kv[$joinField]] = $kv[$filterField]; + } + + /* Associate Target -> Parent (via Intermediate) */ + foreach ($hO[$targetFactory->getModelName()] as $targetObject) { + $parent = $i2p[$targetObject->getKeyValueDict()[$joinField]]; + $retval[$parent][] = $targetObject; + } + + return $retval; + } /** * Retrieve permissions based on class and method requested @@ -179,7 +281,7 @@ public function getRequiredPermissions(string $method): array { $model = $this->getDBAclass(); # Get required permission based on API method type - switch(strtoupper($method)) { + switch (strtoupper($method)) { case "GET": $required_perm = $model::PERM_READ; break; @@ -198,11 +300,15 @@ public function getRequiredPermissions(string $method): array return array($required_perm); } - /** * API entry point for deletion of single object */ public function deleteOne(Request $request, Response $response, array $args): Response + // TODO how to handle cascading deletes? + // ex. Hash foreignkey to hashlist can't be null, but hashlist delete doesnt cascade to Hash + // Which effectively means that we cant delete a hashlist because of foreingkey constraints + // Solution 1: make cascading rules in Database + // Solution 2: implement delete logic in every api model { $this->preCommon($request); $object = $this->doFetch($request, $args['id']); @@ -214,7 +320,6 @@ public function deleteOne(Request $request, Response $response, array $args): Re ->withHeader("Content-Type", "application/json"); } - /** * Request single object from database & validate permissons */ @@ -231,78 +336,227 @@ protected function doFetch(Request $request, string $pk): mixed /** * Additional filtering required for limiting access to objects */ - protected function getFilterACL(): array { + protected function getFilterACL(): array + { return []; } + /** + * Helper function to determine if $resourceRecord is a valid resource record + * returns true if it is a valid resource record and false if it is an invallid resource record + */ + final protected function validateResourceRecord(mixed $resourceRecord): bool + { + return (isset($resourceRecord['type']) && is_numeric($resourceRecord['id'])); + } - /** + final protected function ResourceRecordArrayToUpdateArray($data, $parentId) + { + $updates = []; + foreach ($data as $item) { + if (!$this->validateResourceRecord($item)) { + $encoded_item = json_encode($item); + throw new HttpErrorException('Invallid resource record given in list! invalid resource record: ' . $encoded_item); + } + $updates[] = new MassUpdateSet($item["id"], $parentId); + } + return $updates; + } + + /** * API entry point for requesting multiple objects */ - public function get(Request $request, Response $response, array $args): Response + public static function getManyResources(object $apiClass, Request $request, Response $response, array $relationFs = []): Response { - $this->preCommon($request); + $apiClass->preCommon($request); - $aliasedfeatures = $this->getAliasedFeatures(); - $factory = $this->getFactory(); + $aliasedfeatures = $apiClass->getAliasedFeatures(); + $factory = $apiClass->getFactory(); + + // TODO: Maximum and default should be configurable per server instance + $defaultPageSize = 10000; + $maxPageSize = 50000; - $startAt = $this->getParam($request, 'startsAt', 0); - $maxResults = $this->getParam($request, 'maxResults', 5); + $pageAfter = $apiClass->getQueryParameterFamilyMember($request, 'page', 'after') ?? 0; + $pageSize = $apiClass->getQueryParameterFamilyMember($request, 'page', 'size') ?? $defaultPageSize; + if ($pageSize < 0) { + throw new HttpErrorException("Invallid parameter, page[size] must be a positive integer", 400); + } elseif ($pageSize > $maxPageSize) { + throw new HttpErrorException(sprintf("You requested a size of %d, but %d is the maximum.", $pageSize, $maxPageSize), 400); + } + + $validExpandables = $apiClass::getExpandables(); + $expands = $apiClass->makeExpandables($request, $validExpandables); - $validExpandables = $this->getExpandables(); - $expands = $this->makeExpandables($request, $validExpandables); - $expandable = array_diff($validExpandables, $expands); + /* Object filter definition */ + $aFs = []; /* Generate filters */ - $qFs_Filter = $this->makeFilter($request, $aliasedfeatures); - $qFs_ACL = $this->getFilterACL(); + $qFs_Filter = $apiClass->makeFilter($request, $aliasedfeatures); + $qFs_ACL = $apiClass->getFilterACL(); $qFs = array_merge($qFs_ACL, $qFs_Filter); + if (count($qFs) > 0) { + $aFs[Factory::FILTER] = $qFs; + } - $oFs = $this->makeOrderFilter($request, $aliasedfeatures); + /** + * Create pagination + * + * TODO: Deny pagination with un-stable sorting + */ + $defaultSort = $apiClass->getQueryParameterFamilyMember($request, 'page', 'after') == null && + $apiClass->getQueryParameterFamilyMember($request, 'page', 'before') != null ? 'DESC' : 'ASC'; + $orderTemplates = $apiClass->makeOrderFilterTemplates($request, $aliasedfeatures, $defaultSort); - /* Generate query */ - $allFilters = []; - if (count($qFs) > 0) { - $allFilters[Factory::FILTER] = $qFs; + // Build actual order filters + foreach ($orderTemplates as $orderTemplate) { + $aFs[Factory::ORDER][] = new OrderFilter($orderTemplate['by'], $orderTemplate['type']); } - if (count($oFs) > 0) { - $allFilters[Factory::ORDER] = $oFs; + + /* Include relation filters */ + $finalFs = array_merge($aFs, $relationFs); + + //according to JSON API spec, first and last have to be calculated if inexpensive to compute + //(https://jsonapi.org/profiles/ethanresnick/cursor-pagination/#auto-id-links)) + //if this query is too expensive for big tables, it should be removed + $max = $factory->minMaxFilter($finalFs, $apiClass->getPrimaryKey(), "MAX"); + + //pagination filters need to be added after max has been calculated + $finalFs[Factory::LIMIT] = new LimitFilter($pageSize); + + $finalFs[Factory::FILTER][] = new QueryFilter($apiClass->getPrimaryKey(), $pageAfter, '>', $factory); + $pageBefore = $apiClass->getQueryParameterFamilyMember($request, 'page', 'before'); + if (isset($pageBefore)) { + $finalFs[Factory::FILTER][] = new QueryFilter($apiClass->getPrimaryKey(), $pageBefore, '<', $factory); } /* Request objects */ - $objects = $factory->filter($allFilters); + $filterObjects = $factory->filter($finalFs); + + /* JOIN statements will return related modules as well, discard for now */ + if (array_key_exists(Factory::JOIN, $finalFs)) { + $objects = $filterObjects[$factory->getModelname()]; + } else { + $objects = $filterObjects; + } /* Resolve all expandables */ $expandResult = []; foreach ($expands as $expand) { // mapping from $objectId -> result objects in - $expandResult[$expand] = $this->fetchExpandObjects($objects, $expand); + $expandResult[$expand] = $apiClass->fetchExpandObjects($objects, $expand); } - /* Convert objects to JSON */ - $lists = []; + /* Convert objects to JSON:API */ + $dataResources = []; + $includedResources = []; + + // Convert objects to data resources foreach ($objects as $object) { - $newObject = $this->applyExpansions($object, $expands, $expandResult); - $lists[] = $newObject; + // Create object + $newObject = $apiClass->obj2Resource($object, $expandResult); + + // For compound document, included resources + foreach ($expands as $expand) { + if (array_key_exists($object->getId(), $expandResult[$expand])) { + $expandResultObject = $expandResult[$expand][$object->getId()]; + if (is_array($expandResultObject)) { + foreach ($expandResultObject as $expandObject) { + $includedResources[] = $apiClass->obj2Resource($expandObject); + } + } else { + if ($expandResultObject === null) { + // to-only relation which is nullable + continue; + } + $includedResources[] = $apiClass->obj2Resource($expandResultObject); + } + } + } + + // Add to result output + $dataResources[] = $newObject; } - // TODO: Implement actual expanding - $total = count($objects); + //build last link + $lastParams = $request->getQueryParams(); + unset($lastParams['page']['after']); + $lastParams['page']['size'] = $pageSize; + $lastParams['page']['before'] = $max + 1; + $linksLast = $request->getUri()->getPath() . '?' . urldecode(http_build_query($lastParams)); + + // Build self link + $selfParams = $request->getQueryParams(); + $selfParams['page']['size'] = $pageSize; + $linksSelf = $request->getUri()->getPath() . '?' . urldecode(http_build_query($selfParams)); + + $linksNext = null; + $linksPrev = null; + + // Build next link + if (!empty($objects)) { + $minId = $maxId = $objects[0]->getId() ?? null; + foreach ($objects as $obj) { + $cur_id = $obj->getId(); + if ($cur_id < $minId) { + $minId = $cur_id; + } + if ($cur_id > $maxId) { + $maxId = $cur_id; + } + } + $nextId = $defaultSort == "ASC" ? $maxId : $minId; + + if ($nextId < $max) { //only set next page when its not the last page + $nextParams = $selfParams; + $nextParams['page']['after'] = $nextId; + unset($nextParams['page']['before']); + $linksNext = $request->getUri()->getPath() . '?' . urldecode(http_build_query($nextParams)); + } + // Build prev link + $prevId = $defaultSort == "DESC" ? $maxId : $minId; + if ($prevId != 1) { //only set previous page when its not the first page + $prevParams = $selfParams; + //This scenario might return a link to an empty array if the elements with the lowest id are deleted, but this is allowed according + //to the json API spec https://jsonapi.org/profiles/ethanresnick/cursor-pagination/#auto-id-links + //We could also get the lowest id the same way we got the max, but this is probably unnecessary expensive. + //But pull request: https://github.com/hashtopolis/server/pull/1069 would create a cheaper way of doing this in a single query + $prevParams['page']['before'] = $prevId; + unset($prevParams['page']['after']); + $linksPrev = $request->getUri()->getPath() . '?' . urldecode(http_build_query($prevParams)); + } + } - $ret = [ - "_expandable" => join(",", $expandable), - "startAt" => $startAt, - "maxResults" => $maxResults, - "total" => $total, - "isLast" => ($total <= ($startAt + $maxResults)), - "values" => array_slice($lists, $startAt, $maxResults) - ]; + //build first link + $firstParams = $request->getQueryParams(); + unset($firstParams['page']['before']); + $firstParams['page']['size'] = $pageSize; + $firstParams['page']['after'] = 0; + $linksFirst = $request->getUri()->getPath() . '?' . urldecode(http_build_query($firstParams)); + $links = [ + "self" => $linksSelf, + "first" => $linksFirst, + "last" => $linksLast, + "next" => $linksNext, + "prev" => $linksPrev, + ]; + + // Generate JSON:API GET output + $ret = self::createJsonResponse($dataResources, $links, $includedResources); $body = $response->getBody(); - $body->write($this->ret2json($ret)); + $body->write($apiClass->ret2json($ret)); return $response->withStatus(200) - ->withHeader("Content-Type", "application/json"); + ->withHeader("Content-Type", 'application/vnd.api+json; ext="https://jsonapi.org/profiles/ethanresnick/cursor-pagination"'); + } + + /** + * API entry point for requesting multiple objects + */ + public function get(Request $request, Response $response, array $args): Response + { + return self::getManyResources($this, $request, $response); } /** @@ -313,29 +567,17 @@ final public function getCreateValidFeatures(): array return $this->getAliasedFeatures(); } - /** * API entry point for requests of single object */ public function getOne(Request $request, Response $response, array $args): Response { $this->preCommon($request); - - $validExpandables = $this->getExpandables(); - $expands = $this->makeExpandables($request, $validExpandables); - $expandable = array_diff($validExpandables, $expands); - $object = $this->doFetch($request, $args['id']); - $ret = $this->object2Array($object, $expands); - $ret["_expandable"] = join(",", $expandable); - ksort($ret); + $classMapper = $this->container->get('classMapper'); - $body = $response->getBody(); - $body->write($this->ret2json($ret)); - - return $response->withStatus(200) - ->withHeader("Content-Type", "application/json"); + return self::getOneResource($this, $object, $request, $response); } @@ -347,46 +589,28 @@ public function patchOne(Request $request, Response $response, array $args): Res $this->preCommon($request); $object = $this->doFetch($request, $args['id']); - $data = $request->getParsedBody(); + $data = $request->getParsedBody()['data']; + if (!$this->validateResourceRecord($data)) { + throw new HttpErrorException('No valid resource identifier object was given as data!', 403); + } $aliasedfeatures = $this->getAliasedFeatures(); - - // Validate incoming data - foreach (array_keys($data) as $key) { - // Ensure key is a regular string - if (is_string($key) == False) { - throw new HttpErrorException("Key '$key' invalid"); - } - // Ensure key exists in target array - if (array_key_exists($key, $aliasedfeatures) == False) { - throw new HttpErrorException("Key '$key' does not exists!"); - } + $attributes = $data['attributes']; + // Validate incoming data + foreach (array_keys($attributes) as $key) { // Ensure key can be updated - if ($aliasedfeatures[$key]['read_only'] == True) { - throw new HttpErrorException("Key '$key' is immutable"); - } - if ($aliasedfeatures[$key]['protected'] == True) { - throw new HttpErrorException("Key '$key' is protected"); - } - if ($aliasedfeatures[$key]['private'] == True) { - throw new HttpErrorException("Key '$key' is private"); - } + $this->isAllowedToMutate($request, $aliasedfeatures, $key); } // Validate input data if it matches the correct type or subtype - $this->validateData($data, $aliasedfeatures); + $this->validateData($attributes, $aliasedfeatures); // This does the real things, patch the values that were sent in the data. - $mappedData = $this->unaliasData($data, $aliasedfeatures); - $this->updateObject($object, $mappedData); + $mappedData = $this->unaliasData($attributes, $aliasedfeatures); + $this->updateObject($object, $mappedData); //TODO updateObject not implemented in every route? // Return updated object $newObject = $this->getFactory()->get($object->getId()); - - $body = $response->getBody(); - $body->write($this->object2JSON($newObject)); - - return $response->withStatus(201) - ->withHeader("Content-Type", "application/json"); + return self::getOneResource($this, $newObject, $request, $response, 201); } @@ -397,32 +621,423 @@ public function post(Request $request, Response $response, array $args): Respons { $this->preCommon($request); - $data = $request->getParsedBody(); + $data = $request->getParsedBody()["data"]; + if ($data == null) { + throw new HttpErrorException("POST request requires data to be present", 403); + } + //POST request RR only needs type, no ID + if (!isset($data['type'])) { + throw new HttpErrorException('No valid resource identifier object with type was given as data!', 403); + } + $attributes = $data["attributes"]; + $allFeatures = $this->getAliasedFeatures(); // Validate incoming parameters - $this->validateParameters($data, $allFeatures); + $this->validateParameters($attributes, $allFeatures); // Validate incoming data by value - $this->validateData($data, $allFeatures); + $this->validateData($attributes, $allFeatures); // Remove key aliases and sanitize to 'db values and request creation - $mappedData = $this->unaliasData($data, $allFeatures); + $mappedData = $this->unaliasData($attributes, $allFeatures); $pk = $this->createObject($mappedData); + // TODO: Return 409 (conflict) if resource already exists or cannot be created + // Request object again, since post-modified entries are not reflected into object. + $object = $this->getFactory()->get($pk); + return self::getOneResource($this, $object, $request, $response, 201); + } + + + /** + * API endpoint to get a to one related resource record + */ + public function getToOneRelatedResource(Request $request, Response $response, array $args): Response + { + $this->preCommon($request); + + $relation = $args['relation']; + + $relationMapper = $this->getToOneRelationships()[$relation]; + $intermediate = $relationMapper["intermediateType"]; + //if there is an intermediate table join on that + if ($intermediate !== null) { + $intermediateFactory = self::getModelFactory($intermediate); + $aFs[Factory::JOIN][] = new JoinFilter( + $intermediateFactory, + $relationMapper['joinField'], + $relationMapper['joinFieldRelation'], + ); + + $factory = $this->getFactory(); + $object = $factory->filter($aFs)[$intermediateFactory->getModelName()][0]; + } else { + // Base object + $object = $this->doFetch($request, $args['id']); + } + + // Relation object + $relationObjects = $this->fetchExpandObjects([$object], $relation); + $relationObject = $relationObjects[$args['id']]; + + $relationClass = $relationMapper['relationType']; + $relationApiClass = new ($this->container->get('classMapper')->get($relationClass))($this->container); + + return self::getOneResource($relationApiClass, $relationObject, $request, $response); + } + + /** + * API endpoint to get a to one relationship link + */ + public function getToOneRelationshipLink(Request $request, Response $response, array $args): Response + { + $this->preCommon($request); + + $relation = $this->getToOneRelationships()[$args['relation']]; + + /* Prepare filter for to-one relations */ + + // Example for Task: + // 'Hashlist' => [ + // 'intermediateType' => TaskWrapper::class, + // 'joinField' => Task::TASK_WRAPPER_ID, + // 'joinFieldRelation' => TaskWrapper::TASK_WRAPPER_ID, + // ], + if (array_key_exists('intermediateType', $relation)) { + $aFs = []; + $intermediateFactory = self::getModelFactory($relation['intermediateType']); + + $aFs[Factory::FILTER][] = new QueryFilter( + $relation['joinField'], + $args['id'], + '=', + $intermediateFactory + ); + + $aFs[Factory::JOIN][] = new JoinFilter( + $intermediateFactory, + $relation['joinField'], + $relation['joinFieldRelation'], + ); + + $factory = $this->getFactory(); + //retrieve the only element of the intermediate table, which contains the data for the relatedResource + $object = $factory->filter($aFs)[$intermediateFactory->getModelName()][0]; + } else { + $object = $this->doFetch($request, $args['id']); + }; + + $id = $object->getKeyValueDict()[$relation['key']]; + + if (is_null($id)) { + $dataResource = null; + } else { + $dataResource = [ + 'type' => $this->getObjectTypeName($relation['relationType']), + 'id' => $id, + ]; + } + + $selfParams = $request->getQueryParams(); + $linksQuery = urldecode(http_build_query($selfParams)); + $linksSelf = $request->getUri()->getPath() . ((!empty($linksQuery)) ? '?' . $linksQuery : ''); + + $apiClass = $this->container->get('classMapper')->get(get_class($object)); + $linksRelated = $this->routeParser->urlFor($apiClass . ':getToOneRelatedResource', $args); + + $links = [ + "self" => $linksSelf, + "related" => $linksRelated, + ]; + + // Generate JSON:API GET output + $ret = self::createJsonResponse($dataResource, $links); + $body = $response->getBody(); - $body->write($this->object2JSON($this->getFactory()->get($pk))); + $body->write($this->ret2json($ret)); + + return $response->withStatus(200) + ->withHeader("Content-Type", 'application/vnd.api+json'); + } + + /* + * API endpoint to patch a to one relationship link + */ + //This works as intended but it can give weird behaviour. ex. it allows you to put an MD5 hash to a SHA1 hashlist + //by patching the foreingkey. Simple fix could be to make foreignkey immutable for cases like this. + public function patchToOneRelationshipLink(Request $request, Response $response, array $args): Response + { + $this->preCommon($request); + $jsonBody = $request->getParsedBody(); + + if ($jsonBody === null || !array_key_exists('data', $jsonBody)) { + throw new HttpErrorException('No data was sent! Send the json data in the following format: {"data": {"type": "foo", "id": 1}}'); + } + $data = $jsonBody['data']; + + $relationKey = $this->getToOneRelationships()[$args['relation']]['relationKey']; + if ($relationKey == null) { + throw new HttpErrorException("Relation does not exist!"); + } + + $features = $this->getFeatures(); + $this->isAllowedToMutate($request, $features, $relationKey); + + $factory = $this->getFactory(); + $object = $this->doFetch($request, intval($args['id'])); + if ($data == null) { + $factory->set($object, $relationKey, null); + } elseif (!$this->validateResourceRecord($data)) { + throw new HttpErrorException('No valid resource identifier object was given as data!'); + } else { + $factory->set($object, $relationKey, $data["id"]); + } + //TODO catch database exceptions like failed foreignkey constraint and return correct error response return $response->withStatus(201) - ->withHeader("Content-Type", "application/json"); + ->withHeader("Content-Type", "application/vnd.api+json"); + } + + + /** + * API endpoint for retrieving to many relationship resource records + */ + public function getToManyRelatedResource(Request $request, Response $response, array $args): Response + { + $this->preCommon($request); + + // Base object -> Relation objects + // $object = $this->doFetch($request, $args['id']); + + $toManyRelation = $this->getToManyRelationships()[$args['relation']]; + $relationClass = $toManyRelation['relationType']; + $relationApiClass = new ($this->container->get('classMapper')->get($relationClass))($this->container); + + $aFs = []; + $filterField = $toManyRelation['relationKey']; + $filterFactory = null; + + if (array_key_exists('junctionTableType', $toManyRelation)) { + $filterField = $toManyRelation['junctionTableFilterField']; + $filterFactory = self::getModelFactory($toManyRelation['junctionTableType']); + + $aFs[Factory::JOIN][] = new JoinFilter( + self::getModelFactory($toManyRelation['junctionTableType']), + $toManyRelation['junctionTableJoinField'], + $toManyRelation['key'], + ); + } + + $aFs[Factory::FILTER][] = new QueryFilter( + $filterField, + $args['id'], + '=', + $filterFactory + ); + + return self::getManyResources($relationApiClass, $request, $response, $aFs); + } + + + /** + * API get request to retrieve the to many relationship links + */ + public function getToManyRelationshipLink(Request $request, Response $response, array $args): Response + { + $this->preCommon($request); + + // Base object -> Relationship objects + $object = $this->doFetch($request, $args['id']); + $expandObjects = $this->fetchExpandObjects([$object], $args['relation']); + + $dataResources = []; + if (array_key_exists($object->getId(), $expandObjects)) { + foreach ($expandObjects[$object->getId()] as $relationshipObject) { + $dataResources[] = [ + 'type' => $this->getObjectTypeName($relationshipObject), + 'id' => $relationshipObject->getId(), + ]; + } + } + + $selfParams = $request->getQueryParams(); + $linksQuery = urldecode(http_build_query($selfParams)); + $linksSelf = $request->getUri()->getPath() . ((!empty($linksQuery)) ? '?' . $linksQuery : ''); + + $apiClass = $this->container->get('classMapper')->get(get_class($object)); + $linksRelated = $this->routeParser->urlFor($apiClass . ':getToManyRelatedResource', $args); + + + // TODO implement pagination support + $linksNext = null; + + // Generate JSON:API GET output + $links = [ + "self" => $linksSelf, + "related" => $linksRelated, + "next" => $linksNext, + ]; + $ret = self::createJsonResponse($dataResources, $links); + + $body = $response->getBody(); + $body->write($this->ret2json($ret)); + + return $response->withStatus(200) + ->withHeader("Content-Type", 'application/vnd.api+json; ext="https://jsonapi.org/profiles/ethanresnick/cursor-pagination"'); + } + + /** + * PATCH request to patch the to many relationship link TODO: handle intermediate tables + */ + public function patchToManyRelationshipLink(Request $request, Response $response, array $args): Response + { + $this->preCommon($request); + $jsonBody = $request->getParsedBody(); + + if ($jsonBody === null || !array_key_exists('data', $jsonBody) || !is_array($jsonBody['data'])) { + throw new HttpErrorException('No data was sent! Send the json data in the following format: {"data":[{"type": "foo", "id": 1}}]'); + } + $data = $jsonBody['data']; + + $relation = $this->getToManyRelationships()[$args['relation']]; + $primaryKey = $this->getPrimaryKeyOther($relation['relationType']); + $relationKey = $relation['relationKey']; + if ($relationKey == null) { + throw new HttpErrorException("Relation does not exist!"); + } + + $relationType = $relation['relationType']; + $features = $this->getFeaturesOther($relationType); + $this->isAllowedToMutate($request, $features, $relationKey); + + $factory = self::getModelFactory($relationType); + + $qF = new QueryFilter($relationKey, $args['id'], "="); + $models = $factory->filter([Factory::FILTER => $qF]); + //TODO Would be nicer if filter/factory could return a dict based on primarykeys directly + $modelsDict = array(); + foreach ($models as $item) { + $modelsDict[$item->getPrimaryKeyValue()] = $item; + } + + $updates = []; + foreach ($data as $item) { + if (!$this->validateResourceRecord($item)) { + $encoded_item = json_encode($item); + throw new HttpErrorException('Invallid resource record given in list! invalid resource record: ' . $encoded_item); + } + $updates[] = new MassUpdateSet($item["id"], $args["id"]); + unset($modelsDict[$item["id"]]); + } + + $leftover_primarykeys = array_keys($modelsDict); + if ($features[$relationKey]["null"] == False && count($leftover_primarykeys) > 0) { + throw new HttpErrorException("Not all current relationship objects have been included, + but the foreignkey can't be set to null. Either add all objects or delete the not needed objects"); + } + foreach ($leftover_primarykeys as $key) { + //set all foreignkeys of current relationships to null that have not been included + $updates[] = new MassUpdateSet($key, null); + } + $factory->getDB()->beginTransaction(); //start transaction to be able roll back + $factory->massSingleUpdate($primaryKey, $relationKey, $updates); + if (!$factory->getDB()->commit()) { + throw new HttpErrorException("Was not able to update to many relationship"); + } + + return $response->withStatus(204) + ->withHeader("Content-Type", "application/vnd.api+json"); + } + + /** + * POST request for the to many relationship link TODO + */ + public function postToManyRelationshipLink(Request $request, Response $response, array $args): Response + { + $this->preCommon($request); + + $jsonBody = $request->getParsedBody(); + if ($jsonBody === null || !array_key_exists('data', $jsonBody) || !is_array($jsonBody['data'])) { + throw new HttpErrorException('No data was sent! Send the json data in the following format: {"data":[{"type": "foo", "id": 1}}]'); + } + $data = $jsonBody['data']; + + $relation = $this->getToManyRelationships()[$args['relation']]; + $relationKey = $relation['relationKey']; + if ($relationKey == null) { + throw new HttpErrorException("Relation does not exist!"); + } + + $relationType = $relation['relationType']; + $primaryKey = $this->getPrimaryKeyOther($relationType); + $features = $this->getFeaturesOther($relationType); + + // $this->checkForeignkeyPermission($request, $relationKey, $features); + $this->isAllowedToMutate($request, $features, $relationKey); + + $factory = self::getModelFactory($relationType); + $updates = self::ResourceRecordArrayToUpdateArray($data, $args["id"]); + $factory->massSingleUpdate($primaryKey, $relationKey, $updates); + + return $response->withStatus(201) + ->withHeader("Content-Type", "application/vnd.api+json"); + } + + /** + * DELETE request for the to many relationship link + * currently there is no object that can be altered this way because of constraints + */ + public function deleteToManyRelationshipLink(Request $request, Response $response, array $args): Response + { + $this->preCommon($request); + $jsonBody = $request->getParsedBody(); + + if ($jsonBody === null || !array_key_exists('data', $jsonBody) && is_array($jsonBody['data'])) { + throw new HttpErrorException('No data was sent! Send the json data in the following format: {"data":[{"type": "foo", "id": 1}}]'); + } + + $relation = $this->getToManyRelationships()[$args['relation']]; + $primaryKey = $relation['key']; + $relationKey = $relation['relationKey']; + if ($relationKey == null) { + throw new HttpErrorException("Relation does not exist!"); + } + + $relationType = $relation['relationType']; + $features = $this->getFeaturesOther($relationType); + $this->isAllowedToMutate($request, $features, $relationKey); + if ($features[$relationKey]['null'] == False) { + // In this scenario another solution could be to delete object TODO? + throw new HttpForbiddenException($request, "Key '$relationKey' cant be set to null"); + } + + $data = $jsonBody['data']; + + foreach ($data as $item) { + if (!$this->validateResourceRecord($item)) { + $encoded_item = json_encode($item); + throw new HttpErrorException('Invalid resource record given in list! invalid resource record: ' . $encoded_item); + } + $updates[] = new MassUpdateSet($item["id"], null); + } + $factory = self::getModelFactory($relationType); + $factory->getDB()->beginTransaction(); //start transaction to be able roll back + $factory->massSingleUpdate($primaryKey, $relationKey, $updates); + if (!$factory->getDB()->commit()) { + throw new HttpErrorException("Some resources failed updating"); + } + + return $response->withStatus(201) + ->withHeader("Content-Type", "application/vnd.api+json"); } /** * Update object with provided values */ - public function updateObject(object $object, array $data, array $processed = []): void + protected function updateObject(object $object, array $data, array $processed = []): void { // Apply changes foreach ($data as $key => $value) { @@ -454,7 +1069,7 @@ final public function getPatchValidFeatures(): array if ($feature['private'] == True) { continue; } - + $validFeatures[$name] = $feature; }; @@ -470,9 +1085,12 @@ final public function getPatchValidFeatures(): array static public function register($app): void { $me = get_called_class(); + $foo = $me::getDBAClass(); $baseUri = $me::getBaseUri(); $baseUriOne = $baseUri . '/{id:[0-9]+}'; + $baseUriRelationships = $baseUri . '/{id:[0-9]+}/relationships'; + $classMapper = $app->getContainer()->get('classMapper'); $classMapper->add($me::getDBAclass(), $me); @@ -490,6 +1108,22 @@ static public function register($app): void $app->get($baseUri, $me . ':get')->setname($me . ':get'); } + foreach ($me::getToOneRelationships() as $name => $relationship) { + $relationUri = '{relation:' . $name . '}'; + $app->get($baseUriOne . '/' . $relationUri, $me . ':getToOneRelatedResource')->setname($me . ':getToOneRelatedResource'); + $app->get($baseUriRelationships . '/' . $relationUri, $me . ':getToOneRelationshipLink')->setname($me . ':getToOneRelationshipLink'); + $app->patch($baseUriRelationships . '/' . $relationUri, $me . ':patchToOneRelationshipLink')->setname($me . ':patchToOneRelationshipLink'); + } + + foreach ($me::getToManyRelationships() as $name => $relationship) { + $relationUri = '{relation:' . $name . '}'; + $app->get($baseUriOne . '/' . $relationUri, $me . ':getToManyRelatedResource')->setname($me . ':getToManyRelatedResource'); + $app->get($baseUriRelationships . '/' . $relationUri, $me . ':getToManyRelationshipLink')->setname($me . ':getToManyRelationshipLink'); + $app->patch($baseUriRelationships . '/' . $relationUri, $me . ':patchToManyRelationshipLink')->setname($me . ':patchToManyRelationshipLink'); + $app->post($baseUriRelationships . '/' . $relationUri, $me . ':postToManyRelationshipLink')->setname($me . ':postToManyRelationshipLink'); + $app->delete($baseUriRelationships . '/' . $relationUri, $me . ':deleteToManyRelationshipLink')->setname($me . ':deleteToManyRelationshipLink'); + } + if (in_array("POST", $available_methods)) { $app->post($baseUri, $me . ':post')->setname($me . ':post'); } @@ -507,5 +1141,3 @@ static public function register($app): void } } } - - diff --git a/src/inc/apiv2/common/openAPISchema.routes.php b/src/inc/apiv2/common/openAPISchema.routes.php index caaa2b04b..b70fcefe7 100644 --- a/src/inc/apiv2/common/openAPISchema.routes.php +++ b/src/inc/apiv2/common/openAPISchema.routes.php @@ -349,6 +349,17 @@ function makeProperties($features): array { "description" => "successfully deleted", ]; + /* Empty JSON object required */ + $paths[$path][$method]["requestBody"] = [ + "required" => true, + "content" => [ + "application/json" => [], + ]]; + } elseif ($method == 'post') { + $paths[$path][$method]["responses"]["204"] = [ + "description" => "successfully created", + ]; + /* Empty JSON object required */ $paths[$path][$method]["requestBody"] = [ "required" => true, diff --git a/src/inc/apiv2/helper/abortChunk.routes.php b/src/inc/apiv2/helper/abortChunk.routes.php index 5d44c7058..9978b4a29 100644 --- a/src/inc/apiv2/helper/abortChunk.routes.php +++ b/src/inc/apiv2/helper/abortChunk.routes.php @@ -24,7 +24,7 @@ public function getFormFields(): array { ]; } - public function actionPost(array $data): array|null { + public function actionPost(array $data): object|array|null { $chunk = self::getChunk($data[Chunk::CHUNK_ID]); TaskUtils::abortChunk($chunk->getId(), $this->getCurrentUser()); diff --git a/src/inc/apiv2/helper/assignAgent.routes.php b/src/inc/apiv2/helper/assignAgent.routes.php index 7597412b0..7abef2223 100644 --- a/src/inc/apiv2/helper/assignAgent.routes.php +++ b/src/inc/apiv2/helper/assignAgent.routes.php @@ -25,7 +25,7 @@ public function getFormFields(): array { ]; } - public function actionPost($data): array|null { + public function actionPost($data): object|array|null { AgentUtils::assign($data[Agent::AGENT_ID], $data[Task::TASK_ID], $this->getCurrentUser()); # TODO: Check how to handle custom return messages that are not object, probably we want that to be in some kind of standardized form. diff --git a/src/inc/apiv2/helper/createSuperHashlist.routes.php b/src/inc/apiv2/helper/createSuperHashlist.routes.php index 09717f0e4..a2139b761 100644 --- a/src/inc/apiv2/helper/createSuperHashlist.routes.php +++ b/src/inc/apiv2/helper/createSuperHashlist.routes.php @@ -34,7 +34,7 @@ public function getFormFields(): array ]; } - public function actionPost($data): array|null { + public function actionPost($data): object|array|null { /* Validate incoming hashlists */ $hashlistIds = []; foreach($data["hashlistIds"] as $hashlistId) { @@ -53,7 +53,8 @@ public function actionPost($data): array|null { assert(count($objects) > 0); /* TODO: Make it bit more transparant and auto-expands hashlists by default */ - return $this->object2Array($objects[0]); + return $objects[0]; + } } diff --git a/src/inc/apiv2/helper/createSupertask.routes.php b/src/inc/apiv2/helper/createSupertask.routes.php index 64499ab72..8ac7de5d0 100644 --- a/src/inc/apiv2/helper/createSupertask.routes.php +++ b/src/inc/apiv2/helper/createSupertask.routes.php @@ -34,7 +34,7 @@ public function getFormFields(): array ]; } - public function actionPost($data): array|null { + public function actionPost($data): object|array|null { $supertaskTemplate = self::getSupertask($data["supertaskTemplateId"]); $hashlist = self::getHashlist($data[Hashlist::HASHLIST_ID]); $crackerBinary = self::getCrackerBinary($data["crackerVersionId"]); @@ -55,7 +55,7 @@ public function actionPost($data): array|null { $objects = self::getModelFactory(TaskWrapper::class)->filter([Factory::FILTER => $qFs, Factory::ORDER => $oF]); assert(count($objects) > 0); - return $this->object2Array($objects[0]); + return $objects[0]; } } diff --git a/src/inc/apiv2/helper/exportCrackedHashes.routes.php b/src/inc/apiv2/helper/exportCrackedHashes.routes.php index 9721b82c6..7050d571a 100644 --- a/src/inc/apiv2/helper/exportCrackedHashes.routes.php +++ b/src/inc/apiv2/helper/exportCrackedHashes.routes.php @@ -26,11 +26,11 @@ public function getFormFields(): array ]; } - public function actionPost($data): array|null { + public function actionPost($data): object|array|null { $hashlist = self::getHashlist($data[Hashlist::HASHLIST_ID]); $file = HashlistUtils::export($hashlist->getId(), $this->getCurrentUser()); - return $this->object2Array($file); + return $file; } } diff --git a/src/inc/apiv2/helper/exportLeftHashes.routes.php b/src/inc/apiv2/helper/exportLeftHashes.routes.php index fc19578c3..582404ad1 100644 --- a/src/inc/apiv2/helper/exportLeftHashes.routes.php +++ b/src/inc/apiv2/helper/exportLeftHashes.routes.php @@ -26,12 +26,12 @@ public function getFormFields(): array ]; } - public function actionPost($data): array|null { + public function actionPost($data): object|array|null { $hashlist = self::getHashlist($data[Hashlist::HASHLIST_ID]); $file = HashlistUtils::leftlist($hashlist->getId(), $this->getCurrentUser()); - return $this->object2Array($file); + return $file; } } diff --git a/src/inc/apiv2/helper/exportWordlist.routes.php b/src/inc/apiv2/helper/exportWordlist.routes.php index 9f53e1d83..56d1b58be 100644 --- a/src/inc/apiv2/helper/exportWordlist.routes.php +++ b/src/inc/apiv2/helper/exportWordlist.routes.php @@ -26,12 +26,12 @@ public function getFormFields(): array ]; } - public function actionPost($data): array|null { + public function actionPost($data): object|array|null { $hashlist = self::getHashlist($data[Hashlist::HASHLIST_ID]); $arr = HashlistUtils::createWordlists($hashlist->getId(), $this->getCurrentUser()); - return $this->object2Array($arr[2]); + return $arr[2]; } } diff --git a/src/inc/apiv2/helper/importCrackedHashes.routes.php b/src/inc/apiv2/helper/importCrackedHashes.routes.php index ee375d2d3..6f99e0481 100644 --- a/src/inc/apiv2/helper/importCrackedHashes.routes.php +++ b/src/inc/apiv2/helper/importCrackedHashes.routes.php @@ -26,12 +26,11 @@ public function getFormFields(): array { ]; } - public function actionPost($data): array|null { + public function actionPost($data): object|array|null { $hashlist = self::getHashlist($data[Hashlist::HASHLIST_ID]); $result = HashlistUtils::processZap($hashlist->getId(), $data["separator"], "paste", ["hashfield" => $data["sourceData"]], [], $this->getCurrentUser()); - # TODO: Check how to handle custom return messages that are not object, probably we want that to be in some kind of standardized form. return [ "totalLines" => $result[0], "newCracked" => $result[1], diff --git a/src/inc/apiv2/helper/purgeTask.routes.php b/src/inc/apiv2/helper/purgeTask.routes.php index 1bd264b21..d7f308d57 100644 --- a/src/inc/apiv2/helper/purgeTask.routes.php +++ b/src/inc/apiv2/helper/purgeTask.routes.php @@ -25,7 +25,7 @@ public function getFormFields(): array ]; } - public function actionPost($data): array|null { + public function actionPost($data): object|array|null { $task = self::getTask($data[Task::TASK_ID]); TaskUtils::purgeTask($task->getId(), $this->getCurrentUser()); diff --git a/src/inc/apiv2/helper/recountFileLines.routes.php b/src/inc/apiv2/helper/recountFileLines.routes.php index f737b984e..d3bb0fd63 100644 --- a/src/inc/apiv2/helper/recountFileLines.routes.php +++ b/src/inc/apiv2/helper/recountFileLines.routes.php @@ -24,7 +24,7 @@ public function getFormFields(): array ]; } - public function actionPost($data): array|null { + public function actionPost($data): object|array|null { // first retrieve the file, as fileCountLines does not check any permissions, therfore to be sure call getFile() first, even if it is not required technically FileUtils::getFile($data[File::FILE_ID], $this->getCurrentUser()); diff --git a/src/inc/apiv2/helper/resetChunk.routes.php b/src/inc/apiv2/helper/resetChunk.routes.php index 00870b5ba..1cec7e7fa 100644 --- a/src/inc/apiv2/helper/resetChunk.routes.php +++ b/src/inc/apiv2/helper/resetChunk.routes.php @@ -24,7 +24,7 @@ public function getFormFields(): array { ]; } - public function actionPost(array $data): array|null { + public function actionPost(array $data): object|array|null { $chunk = self::getChunk($data[Chunk::CHUNK_ID]); TaskUtils::resetChunk($chunk->getId(), $this->getCurrentUser()); return null; diff --git a/src/inc/apiv2/helper/setUserPassword.routes.php b/src/inc/apiv2/helper/setUserPassword.routes.php index a05d466b7..5a19326f7 100644 --- a/src/inc/apiv2/helper/setUserPassword.routes.php +++ b/src/inc/apiv2/helper/setUserPassword.routes.php @@ -28,7 +28,7 @@ public function getFormFields(): array ]; } - public function actionPost($data): array|null { + public function actionPost($data): object|array|null { $user = self::getUser($data[User::USER_ID]); /* Set user password if provided */ diff --git a/src/inc/apiv2/helper/unassignAgent.routes.php b/src/inc/apiv2/helper/unassignAgent.routes.php index cfdda8080..53cd43e40 100644 --- a/src/inc/apiv2/helper/unassignAgent.routes.php +++ b/src/inc/apiv2/helper/unassignAgent.routes.php @@ -24,7 +24,7 @@ public function getFormFields(): array { ]; } - public function actionPost($data): array|null { + public function actionPost($data): object|array|null { AgentUtils::assign($data[Agent::AGENT_ID], 0, $this->getCurrentUser()); # TODO: Check how to handle custom return messages that are not object, probably we want that to be in some kind of standardized form. diff --git a/src/inc/apiv2/model/accessgroups.routes.php b/src/inc/apiv2/model/accessgroups.routes.php index 50d8a4fa5..f6f5cb5cb 100644 --- a/src/inc/apiv2/model/accessgroups.routes.php +++ b/src/inc/apiv2/model/accessgroups.routes.php @@ -19,38 +19,31 @@ public static function getDBAclass(): string { return AccessGroup::class; } - public function getExpandables(): array { - return ["userMembers", "agentMembers"]; + public static function getToManyRelationships(): array { + return [ + 'userMembers' => [ + 'key' => AccessGroup::ACCESS_GROUP_ID, + + 'junctionTableType' => AccessGroupUser::class, + 'junctionTableFilterField' => AccessGroupUser::ACCESS_GROUP_ID, + 'junctionTableJoinField' => AccessGroupUser::USER_ID, + + 'relationType' => User::class, + 'relationKey' => User::USER_ID, + ], + 'agentMembers' => [ + 'key' => AccessGroup::ACCESS_GROUP_ID, + + 'junctionTableType' =>AccessGroupAgent::class, + 'junctionTableFilterField' => AccessGroupAgent::ACCESS_GROUP_ID, + 'junctionTableJoinField' => AccessGroupAgent::AGENT_ID, + + 'relationType' => Agent::class, + 'relationKey' => Agent::AGENT_ID, + ], + ]; } - protected function fetchExpandObjects(array $objects, string $expand): mixed { - /* Ensure we receive the proper type */ - array_walk($objects, function($obj) { assert($obj instanceof AccessGroup); }); - - /* Expand requested section */ - switch($expand) { - case 'userMembers': - return $this->getManyToOneRelationViaIntermediate( - $objects, - AccessGroup::ACCESS_GROUP_ID, - Factory::getAccessGroupUserFactory(), - AccessGroupUser::ACCESS_GROUP_ID, - Factory::getUserFactory(), - User::USER_ID - ); - case 'agentMembers': - return $this->getManyToOneRelationViaIntermediate( - $objects, - AccessGroup::ACCESS_GROUP_ID, - Factory::getAccessGroupAgentFactory(), - AccessGroupAgent::ACCESS_GROUP_ID, - Factory::getAgentFactory(), - Agent::AGENT_ID - ); - default: - throw new BadFunctionCallException("Internal error: Expansion '$expand' not implemented!"); - } - } protected function createObject(array $data): int { $object = AccessGroupUtils::createGroup($data[AccessGroup::GROUP_NAME]); diff --git a/src/inc/apiv2/model/agentassignments.routes.php b/src/inc/apiv2/model/agentassignments.routes.php index d66a3dfa6..05e1645b8 100644 --- a/src/inc/apiv2/model/agentassignments.routes.php +++ b/src/inc/apiv2/model/agentassignments.routes.php @@ -23,34 +23,22 @@ public static function getDBAclass(): string { return Assignment::class; } - public function getExpandables(): array { - return ["task", "agent"]; - } + public static function getToOneRelationships(): array { + return [ + 'agent' => [ + 'key' => Assignment::AGENT_ID, - protected function fetchExpandObjects(array $objects, string $expand): mixed { - /* Ensure we receive the proper type */ - array_walk($objects, function($obj) { assert($obj instanceof Assignment); }); + 'relationType' => Agent::class, + 'relationKey' => Agent::AGENT_ID, + ], + 'task' => [ + 'key' => Assignment::TASK_ID, - /* Expand requested section */ - switch($expand) { - case 'task': - return $this->getForeignKeyRelation( - $objects, - Assignment::TASK_ID, - Factory::getTaskFactory(), - Task::TASK_ID - ); - case 'agent': - return $this->getForeignKeyRelation( - $objects, - Assignment::AGENT_ID, - Factory::getAgentFactory(), - Agent::AGENT_ID - ); - default: - throw new BadFunctionCallException("Internal error: Expansion '$expand' not implemented!"); - } - } + 'relationType' => Task::class, + 'relationKey' => Task::TASK_ID, + ], + ]; + } protected function createObject(array $data): int { AgentUtils::assign($data[Assignment::AGENT_ID], $data[Assignment::TASK_ID], $this->getCurrentUser()); diff --git a/src/inc/apiv2/model/agents.routes.php b/src/inc/apiv2/model/agents.routes.php index b46a4779b..3dbc00c1f 100644 --- a/src/inc/apiv2/model/agents.routes.php +++ b/src/inc/apiv2/model/agents.routes.php @@ -22,36 +22,27 @@ public static function getDBAclass(): string { return Agent::class; } - public function getExpandables(): array { - return ['accessGroups', 'agentstats']; + public static function getToManyRelationships(): array { + return [ + 'accessGroups' => [ + 'key' => Agent::AGENT_ID, + + 'junctionTableType' => AccessGroupAgent::class, + 'junctionTableFilterField' => AccessGroupAgent::AGENT_ID, + 'junctionTableJoinField' => AccessGroupAgent::ACCESS_GROUP_ID, + + 'relationType' => AccessGroup::class, + 'relationKey' => AccessGroup::ACCESS_GROUP_ID, + ], + 'agentStats' => [ + 'key' => Agent::AGENT_ID, + + 'relationType' => AgentStat::class, + 'relationKey' => AgentStat::AGENT_ID, + ], + + ]; } - - protected function fetchExpandObjects(array $objects, string $expand): mixed { - /* Ensure we receive the proper type */ - array_walk($objects, function($obj) { assert($obj instanceof Agent); }); - - /* Expand requested section */ - switch($expand) { - case 'accessGroups': - return $this->getManyToOneRelationViaIntermediate( - $objects, - Agent::AGENT_ID, - Factory::getAccessGroupAgentFactory(), - AccessGroupAgent::AGENT_ID, - Factory::getAccessGroupFactory(), - AccessGroup::ACCESS_GROUP_ID - ); - case 'agentstats': - return $this->getManyToOneRelation( - $objects, - Agent::AGENT_ID, - Factory::getAgentStatFactory(), - AgentStat::AGENT_ID - ); - default: - throw new BadFunctionCallException("Internal error: Expansion '$expand' not implemented!"); - } - } protected function createObject(array $data): int { assert(False, "Chunks cannot be created via API"); diff --git a/src/inc/apiv2/model/agentstats.routes.php b/src/inc/apiv2/model/agentstats.routes.php index 11c9a4c3a..5aa59b823 100644 --- a/src/inc/apiv2/model/agentstats.routes.php +++ b/src/inc/apiv2/model/agentstats.routes.php @@ -6,7 +6,7 @@ require_once(dirname(__FILE__) . "/../common/AbstractModelAPI.class.php"); -class AgentStatsAPI extends AbstractModelAPI { +class AgentStatAPI extends AbstractModelAPI { public static function getBaseUri(): string { return "/api/v2/ui/agentstats"; } @@ -33,4 +33,4 @@ protected function deleteObject(object $object): void { } } -AgentStatsAPI::register($app); \ No newline at end of file +AgentStatAPI::register($app); \ No newline at end of file diff --git a/src/inc/apiv2/model/chunks.routes.php b/src/inc/apiv2/model/chunks.routes.php index a4d2310c9..92df57022 100644 --- a/src/inc/apiv2/model/chunks.routes.php +++ b/src/inc/apiv2/model/chunks.routes.php @@ -1,6 +1,7 @@ getForeignKeyRelation( - $objects, - Chunk::TASK_ID, - Factory::getTaskFactory(), - Task::TASK_ID - ); - default: - throw new BadFunctionCallException("Internal error: Expansion '$expand' not implemented!"); - } - } + public static function getToOneRelationships(): array { + return [ + 'agent' => [ + 'key' => Chunk::AGENT_ID, + + 'relationType' => Agent::class, + 'relationKey' => Agent::AGENT_ID, + ], + 'task' => [ + 'key' => Chunk::TASK_ID, + + 'relationType' => Task::class, + 'relationKey' => Task::TASK_ID, + ], + ]; + } protected function createObject(array $data): int { /* Dummy code to implement abstract functions */ @@ -48,7 +44,7 @@ protected function createObject(array $data): int { return -1; } - public function updateObject(object $object, array $data, array $processed = []): void { + protected function updateObject(object $object, array $data, array $processed = []): void { assert(False, "Chunks cannot be updated via API"); } diff --git a/src/inc/apiv2/model/configs.routes.php b/src/inc/apiv2/model/configs.routes.php index 94a6a216f..958944182 100644 --- a/src/inc/apiv2/model/configs.routes.php +++ b/src/inc/apiv2/model/configs.routes.php @@ -20,26 +20,15 @@ public static function getDBAclass(): string { return Config::class; } - public function getExpandables(): array { - return ['configSection']; - } - - protected function fetchExpandObjects(array $objects, string $expand): mixed { - /* Ensure we receive the proper type */ - array_walk($objects, function($obj) { assert($obj instanceof Config); }); - - /* Expand requested section */ - switch($expand) { - case 'configSection': - return $this->getForeignKeyRelation( - $objects, - Config::CONFIG_SECTION_ID, - Factory::getConfigSectionFactory(), - ConfigSection::CONFIG_SECTION_ID, - ); - default: - throw new BadFunctionCallException("Internal error: Expansion '$expand' not implemented!"); - } + public static function getToOneRelationships(): array { + return [ + 'configSection' => [ + 'key' => Config::CONFIG_SECTION_ID, + + 'relationType' => ConfigSection::class, + 'relationKey' => ConfigSection::CONFIG_SECTION_ID, + ], + ]; } protected function createObject(array $data): int { diff --git a/src/inc/apiv2/model/crackers.routes.php b/src/inc/apiv2/model/crackers.routes.php index cd97773b3..119b48bf4 100644 --- a/src/inc/apiv2/model/crackers.routes.php +++ b/src/inc/apiv2/model/crackers.routes.php @@ -5,6 +5,7 @@ use DBA\CrackerBinary; use DBA\CrackerBinaryType; +use DBA\Task; require_once(dirname(__FILE__) . "/../common/AbstractModelAPI.class.php"); @@ -18,26 +19,27 @@ public static function getDBAclass(): string { return CrackerBinary::class; } - public function getExpandables(): array { - return ["crackerBinaryType"]; - } + public static function getToOneRelationships(): array { + return [ + 'crackerBinaryType' => [ + 'key' => CrackerBinary::CRACKER_BINARY_TYPE_ID, - protected function fetchExpandObjects(array $objects, string $expand): mixed { - /* Ensure we receive the proper type */ - array_walk($objects, function($obj) { assert($obj instanceof CrackerBinary); }); + 'relationType' => CrackerBinaryType::class, + 'relationKey' => CrackerBinaryType::CRACKER_BINARY_TYPE_ID, + ], + ]; + } - /* Expand requested section */ - switch($expand) { - case 'crackerBinaryType': - return $this->getForeignKeyRelation( - $objects, - CrackerBinary::CRACKER_BINARY_TYPE_ID, - Factory::getCrackerBinaryTypeFactory(), - CrackerBinaryType::CRACKER_BINARY_TYPE_ID, - ); - default: - throw new BadFunctionCallException("Internal error: Expansion '$expand' not implemented!"); - } + public static function getToManyRelationships(): array + { + return [ + 'tasks' => [ + 'key' => CrackerBinary::CRACKER_BINARY_ID, + + 'relationType' => Task::class, + 'relationKey' => Task::CRACKER_BINARY_ID, + ], + ]; } protected function createObject(array $data): int { diff --git a/src/inc/apiv2/model/crackertypes.routes.php b/src/inc/apiv2/model/crackertypes.routes.php index 6e7a730dc..47dec3eb3 100644 --- a/src/inc/apiv2/model/crackertypes.routes.php +++ b/src/inc/apiv2/model/crackertypes.routes.php @@ -5,6 +5,7 @@ use DBA\CrackerBinary; use DBA\CrackerBinaryType; +use DBA\Task; require_once(dirname(__FILE__) . "/../common/AbstractModelAPI.class.php"); @@ -18,27 +19,24 @@ public static function getDBAclass(): string { return CrackerBinaryType::class; } - public function getExpandables(): array { - return ["crackerVersions"]; - } - - protected function fetchExpandObjects(array $objects, string $expand): mixed { - /* Ensure we receive the proper type */ - array_walk($objects, function($obj) { assert($obj instanceof CrackerBinaryType); }); - /* Expand requested section */ - switch($expand) { - case 'crackerVersions': - return $this->getManyToOneRelation( - $objects, - CrackerBinaryType::CRACKER_BINARY_TYPE_ID, - Factory::getCrackerBinaryFactory(), - CrackerBinary::CRACKER_BINARY_TYPE_ID - ); - default: - throw new BadFunctionCallException("Internal error: Expansion '$expand' not implemented!"); - } + public static function getToManyRelationships(): array { + return [ + 'crackerVersions' => [ + 'key' => CrackerBinaryType::CRACKER_BINARY_TYPE_ID, + + 'relationType' => CrackerBinary::class, + 'relationKey' => CrackerBinary::CRACKER_BINARY_TYPE_ID, + ], + 'tasks' => [ + 'key' => CrackerBinaryType::CRACKER_BINARY_TYPE_ID, + + 'relationType' => Task::class, + 'relationKey' => Task::CRACKER_BINARY_TYPE_ID, + ] + ]; } + protected function createObject(array $data): int { CrackerUtils::createBinaryType($data[CrackerBinaryType::TYPE_NAME]); diff --git a/src/inc/apiv2/model/files.routes.php b/src/inc/apiv2/model/files.routes.php index 8926c1fed..088b309e7 100644 --- a/src/inc/apiv2/model/files.routes.php +++ b/src/inc/apiv2/model/files.routes.php @@ -20,26 +20,15 @@ public static function getDBAclass(): string { return File::class; } - public function getExpandables(): array { - return ["accessGroup"]; - } + public static function getToOneRelationships(): array { + return [ + 'accessGroup' => [ + 'key' => File::ACCESS_GROUP_ID, - protected function fetchExpandObjects(array $objects, string $expand): mixed { - /* Ensure we receive the proper type */ - array_walk($objects, function($obj) { assert($obj instanceof File); }); - - /* Expand requested section */ - switch($expand) { - case 'accessGroup': - return $this->getForeignKeyRelation( - $objects, - File::ACCESS_GROUP_ID, - Factory::getAccessGroupFactory(), - AccessGroup::ACCESS_GROUP_ID - ); - default: - throw new BadFunctionCallException("Internal error: Expansion '$expand' not implemented!"); - } + 'relationType' => AccessGroup::class, + 'relationKey' => AccessGroup::ACCESS_GROUP_ID, + ], + ]; } public function getFormFields(): array { diff --git a/src/inc/apiv2/model/globalpermissiongroups.routes.php b/src/inc/apiv2/model/globalpermissiongroups.routes.php index e99c69868..75ed789b6 100644 --- a/src/inc/apiv2/model/globalpermissiongroups.routes.php +++ b/src/inc/apiv2/model/globalpermissiongroups.routes.php @@ -7,36 +7,25 @@ require_once(dirname(__FILE__) . "/../common/AbstractModelAPI.class.php"); -class GlobalPermissionGroupsAPI extends AbstractModelAPI { +class GlobalPermissionGroupAPI extends AbstractModelAPI { public static function getBaseUri(): string { return "/api/v2/ui/globalpermissiongroups"; } public static function getDBAclass(): string { return RightGroup::class; - } - - public function getExpandables(): array { - return ['user']; } - protected function fetchExpandObjects(array $objects, string $expand): mixed { - /* Ensure we receive the proper type */ - array_walk($objects, function($obj) { assert($obj instanceof RightGroup); }); - - /* Expand requested section */ - switch($expand) { - case 'user': - return $this->getManyToOneRelation( - $objects, - RightGroup::RIGHT_GROUP_ID, - Factory::getUserFactory(), - User::RIGHT_GROUP_ID - ); - default: - throw new BadFunctionCallException("Internal error: Expansion '$expand' not implemented!"); - } - } + public static function getToManyRelationships(): array { + return [ + 'userMembers' => [ + 'key' => RightGroup::RIGHT_GROUP_ID, + + 'relationType' => User::class, + 'relationKey' => User::RIGHT_GROUP_ID, + ], + ]; + } /** * Rewrite permissions DB values to CRUD field values @@ -133,4 +122,4 @@ public function updateObject(object $object, $data, $processed = []): void { } } -GlobalPermissionGroupsAPI::register($app); \ No newline at end of file +GlobalPermissionGroupAPI::register($app); \ No newline at end of file diff --git a/src/inc/apiv2/model/hashes.routes.php b/src/inc/apiv2/model/hashes.routes.php index 97d44cce7..8a1a207b3 100644 --- a/src/inc/apiv2/model/hashes.routes.php +++ b/src/inc/apiv2/model/hashes.routes.php @@ -19,35 +19,23 @@ public static function getAvailableMethods(): array { public static function getDBAclass(): string { return Hash::class; - } - - public function getExpandables(): array { - return ["hashlist", "chunk"]; } - - protected function fetchExpandObjects(array $objects, string $expand): mixed { - /* Ensure we receive the proper type */ - array_walk($objects, function($obj) { assert($obj instanceof Hash); }); - - /* Expand requested section */ - switch($expand) { - case 'hashlist': - return $this->getForeignKeyRelation( - $objects, - Hash::HASHLIST_ID, - Factory::getHashListFactory(), - HashList::HASHLIST_ID - ); - case 'chunk': - return $this->getForeignKeyRelation( - $objects, - Hash::CHUNK_ID, - Factory::getChunkFactory(), - Chunk::CHUNK_ID - ); - default: - throw new BadFunctionCallException("Internal error: Expansion '$expand' not implemented!"); - } + + public static function getToOneRelationships(): array { + return [ + 'chunk' => [ + 'key' => Hash::CHUNK_ID, + + 'relationType' => Chunk::class, + 'relationKey' => Chunk::CHUNK_ID, + ], + 'hashlist' => [ + 'key' => Hash::HASHLIST_ID, + + 'relationType' => Hashlist::class, + 'relationKey' => Hashlist::HASHLIST_ID, + ], + ]; } protected function createObject(array $data): int { diff --git a/src/inc/apiv2/model/hashlists.routes.php b/src/inc/apiv2/model/hashlists.routes.php index 159b4eeef..a44c54c4f 100644 --- a/src/inc/apiv2/model/hashlists.routes.php +++ b/src/inc/apiv2/model/hashlists.routes.php @@ -25,59 +25,55 @@ public static function getDBAclass(): string { return Hashlist::class; } - public function getExpandables(): array { - return ["accessGroup", "hashType", "hashes", "tasks", "hashlists"]; + + public static function getToOneRelationships(): array { + return [ + 'accessGroup' => [ + 'key' => Hashlist::ACCESS_GROUP_ID, + + 'relationType' => AccessGroup::class, + 'relationKey' => AccessGroup::ACCESS_GROUP_ID, + ], + 'hashType' => [ + 'key' => Hashlist::HASH_TYPE_ID, + + 'relationType' => HashType::class, + 'relationKey' => HashType::HASH_TYPE_ID, + ], + ]; } - - protected function fetchExpandObjects(array $objects, string $expand): mixed { - /* Ensure we receive the proper type */ - array_walk($objects, function($obj) { assert($obj instanceof Hashlist); }); - - /* Expand requested section */ - switch($expand) { - case 'accessGroup': - return $this->getForeignKeyRelation( - $objects, - Hashlist::ACCESS_GROUP_ID, - Factory::getAccessGroupFactory(), - AccessGroup::ACCESS_GROUP_ID - ); - case 'hashType': - return $this->getForeignKeyRelation( - $objects, - Hashlist::HASH_TYPE_ID, - Factory::getHashTypeFactory(), - HashType::HASH_TYPE_ID - ); - case 'hashes': - return $this->getManyToOneRelation( - $objects, - Hashlist::HASHLIST_ID, - Factory::getHashFactory(), - Hash::HASHLIST_ID - ); - case 'hashlists': - /* PARENT_HASHLIST_ID in use in intermediate table */ - return $this->getManyToOneRelationViaIntermediate( - $objects, - Hashlist::HASHLIST_ID, - Factory::getHashlistHashlistFactory(), - HashlistHashlist::PARENT_HASHLIST_ID, - Factory::getHashlistFactory(), - Hashlist::HASHLIST_ID, - ); - case 'tasks': - return $this->getManyToOneRelationViaIntermediate( - $objects, - Hashlist::HASHLIST_ID, - Factory::getTaskWrapperFactory(), - TaskWrapper::HASHLIST_ID, - Factory::getTaskFactory(), - Task::TASK_WRAPPER_ID, - ); - default: - throw new BadFunctionCallException("Internal error: Expansion '$expand' not implemented!"); - } + + + public static function getToManyRelationships(): array { + return [ + 'hashes' => [ + 'key' => Hashlist::HASHLIST_ID, + + 'relationType' => Hash::class, + 'relationKey' => Hash::HASHLIST_ID, + ], + /* Special case due to superhashlist setup. PARENT_HASHLIST_ID in use in intermediate table */ + 'hashlists' => [ + 'key' => Hashlist::HASHLIST_ID, + + 'junctionTableType' => HashlistHashlist::class, + 'junctionTableFilterField' => HashlistHashlist::PARENT_HASHLIST_ID, + 'junctionTableJoinField' => HashlistHashlist::HASHLIST_ID, + + 'relationType' => Hashlist::class, + 'relationKey' => Hashlist::HASHLIST_ID, + ], + 'tasks' => [ + 'key' => Hashlist::HASHLIST_ID, + + 'junctionTableType' => TaskWrapper::class, + 'junctionTableFilterField' => TaskWrapper::HASHLIST_ID, + 'junctionTableJoinField' => TaskWrapper::TASK_WRAPPER_ID, + + 'relationType' => Task::class, + 'relationKey' => Task::TASK_WRAPPER_ID, + ], + ]; } protected function getFilterACL(): array { diff --git a/src/inc/apiv2/model/healthcheckagents.routes.php b/src/inc/apiv2/model/healthcheckagents.routes.php index a09d43ad9..e21a312fd 100644 --- a/src/inc/apiv2/model/healthcheckagents.routes.php +++ b/src/inc/apiv2/model/healthcheckagents.routes.php @@ -21,33 +21,21 @@ public static function getDBAclass(): string { return HealthCheckAgent::class; } - public function getExpandables(): array { - return ['agent', 'healthCheck']; - } - - protected function fetchExpandObjects(array $objects, string $expand): mixed { - /* Ensure we receive the proper type */ - array_walk($objects, function($obj) { assert($obj instanceof HealthCheckAgent); }); - - /* Expand requested section */ - switch($expand) { - case 'agent': - return $this->getForeignKeyRelation( - $objects, - HealthCheckAgent::AGENT_ID, - Factory::getAgentFactory(), - Agent::AGENT_ID - ); - case 'healthCheck': - return $this->getForeignKeyRelation( - $objects, - HealthCheckAgent::HEALTH_CHECK_ID, - Factory::getHealthCheckFactory(), - HealthCheck::HEALTH_CHECK_ID - ); - default: - throw new BadFunctionCallException("Internal error: Expansion '$expand' not implemented!"); - } + public static function getToOneRelationships(): array { + return [ + 'agent' => [ + 'key' => HealthCheckAgent::AGENT_ID, + + 'relationType' => Agent::class, + 'relationKey' => Agent::AGENT_ID, + ], + 'healthCheck' => [ + 'key' => HealthCheckAgent::HEALTH_CHECK_ID, + + 'relationType' => HealthCheck::class, + 'relationKey' => HealthCheck::HEALTH_CHECK_ID, + ], + ]; } protected function createObject(array $object): int { diff --git a/src/inc/apiv2/model/healthchecks.routes.php b/src/inc/apiv2/model/healthchecks.routes.php index 0a608d3a4..5d9ba9622 100644 --- a/src/inc/apiv2/model/healthchecks.routes.php +++ b/src/inc/apiv2/model/healthchecks.routes.php @@ -17,35 +17,29 @@ public static function getDBAclass(): string { return HealthCheck::class; } - public function getExpandables(): array { - return ['crackerBinary', 'healthCheckAgents']; + + public static function getToOneRelationships(): array { + return [ + 'crackerBinary' => [ + 'key' => HealthCheck::CRACKER_BINARY_ID, + + 'relationType' => CrackerBinary::class, + 'relationKey' => CrackerBinary::CRACKER_BINARY_ID, + ], + ]; } - - protected function fetchExpandObjects(array $objects, string $expand): mixed { - /* Ensure we receive the proper type */ - array_walk($objects, function($obj) { assert($obj instanceof HealthCheck); }); - /* Expand requested section */ - switch($expand) { - case 'crackerBinary': - return $this->getForeignKeyRelation( - $objects, - HealthCheck::CRACKER_BINARY_ID, - Factory::getCrackerBinaryFactory(), - CrackerBinary::CRACKER_BINARY_ID - ); - case 'healthCheckAgents': - return $this->getManyToOneRelation( - $objects, - HealthCheck::HEALTH_CHECK_ID, - Factory::getHealthCheckAgentFactory(), - HealthCheckAgent::HEALTH_CHECK_ID - ); - default: - throw new BadFunctionCallException("Internal error: Expansion '$expand' not implemented!"); - } + public static function getToManyRelationships(): array { + return [ + 'healthCheckAgents' => [ + 'key' => HealthCheck::HEALTH_CHECK_ID, + + 'relationType' => HealthCheckAgent::class, + 'relationKey' => HealthCheckAgent::HEALTH_CHECK_ID, + ], + ]; } - + protected function createObject(array $data): int { $obj = HealthUtils::createHealthCheck( $data[HealthCheck::HASHTYPE_ID], diff --git a/src/inc/apiv2/model/notifications.routes.php b/src/inc/apiv2/model/notifications.routes.php index a3a262ce9..9c0646be1 100644 --- a/src/inc/apiv2/model/notifications.routes.php +++ b/src/inc/apiv2/model/notifications.routes.php @@ -17,28 +17,18 @@ public static function getDBAclass(): string { return NotificationSetting::class; } - public function getExpandables(): array { - return ['user']; - } - - protected function fetchExpandObjects(array $objects, string $expand): mixed { - /* Ensure we receive the proper type */ - array_walk($objects, function($obj) { assert($obj instanceof NotificationSetting); }); + public static function getToOneRelationships(): array { + return [ + 'user' => [ + 'key' => NotificationSetting::USER_ID, - /* Expand requested section */ - switch($expand) { - case 'user': - return $this->getForeignKeyRelation( - $objects, - NotificationSetting::USER_ID, - Factory::getUserFactory(), - User::USER_ID - ); - default: - throw new BadFunctionCallException("Internal error: Expansion '$expand' not implemented!"); - } + 'relationType' => User::class, + 'relationKey' => User::USER_ID, + ], + ]; } - + + public function getFormFields(): array { return ['actionFilter' => ['type' => 'str(256)']]; } diff --git a/src/inc/apiv2/model/pretasks.routes.php b/src/inc/apiv2/model/pretasks.routes.php index 89a091c7f..34361cbc6 100644 --- a/src/inc/apiv2/model/pretasks.routes.php +++ b/src/inc/apiv2/model/pretasks.routes.php @@ -20,28 +20,19 @@ public static function getDBAclass(): string { return Pretask::class; } - public function getExpandables(): array { - return ["pretaskFiles"]; - } - - protected function fetchExpandObjects(array $objects, string $expand): mixed { - /* Ensure we receive the proper type */ - array_walk($objects, function($obj) { assert($obj instanceof PreTask); }); + public static function getToManyRelationships(): array { + return [ + 'pretaskFiles' => [ + 'key' => Pretask::PRETASK_ID, + + 'junctionTableType' => FilePretask::class, + 'junctionTableFilterField' => FilePretask::PRETASK_ID, + 'junctionTableJoinField' => FilePretask::FILE_ID, - /* Expand requested section */ - switch($expand) { - case 'pretaskFiles': - return $this->getManyToOneRelationViaIntermediate( - $objects, - Pretask::PRETASK_ID, - Factory::getFilePretaskFactory(), - FilePretask::PRETASK_ID, - Factory::getFileFactory(), - File::FILE_ID - ); - default: - throw new BadFunctionCallException("Internal error: Expansion '$expand' not implemented!"); - } + 'relationType' => File::class, + 'relationKey' => File::FILE_ID, + ], + ]; } public function getFormFields(): array { diff --git a/src/inc/apiv2/model/speeds.routes.php b/src/inc/apiv2/model/speeds.routes.php index 3a2761bae..8bbaed5da 100644 --- a/src/inc/apiv2/model/speeds.routes.php +++ b/src/inc/apiv2/model/speeds.routes.php @@ -26,33 +26,22 @@ public static function getDBAclass(): string { return Speed::class; } - public function getExpandables(): array { - return [ 'agent', 'task' ]; - } - - protected function fetchExpandObjects(array $objects, string $expand): mixed { - /* Ensure we receive the proper type */ - array_walk($objects, function($obj) { assert($obj instanceof Speed); }); - /* Expand requested section */ - switch($expand) { - case 'agent': - return $this->getForeignKeyRelation( - $objects, - Speed::AGENT_ID, - Factory::getAgentFactory(), - Agent::AGENT_ID - ); - case 'task': - return $this->getForeignKeyRelation( - $objects, - Speed::TASK_ID, - Factory::getTaskFactory(), - Task::TASK_ID - ); - default: - throw new BadFunctionCallException("Internal error: Expansion '$expand' not implemented!"); - } + public static function getToOneRelationships(): array { + return [ + 'agent' => [ + 'key' => Speed::AGENT_ID, + + 'relationType' => Agent::class, + 'relationKey' => Agent::AGENT_ID, + ], + 'task' => [ + 'key' => Speed::TASK_ID, + + 'relationType' => Task::class, + 'relationKey' => Task::TASK_ID, + ], + ]; } protected function createObject(array $data): int { diff --git a/src/inc/apiv2/model/supertasks.routes.php b/src/inc/apiv2/model/supertasks.routes.php index deec64475..d4d37e146 100644 --- a/src/inc/apiv2/model/supertasks.routes.php +++ b/src/inc/apiv2/model/supertasks.routes.php @@ -19,28 +19,19 @@ public static function getDBAclass(): string { return Supertask::class; } - public function getExpandables(): array { - return [ "pretasks" ]; - } - - protected function fetchExpandObjects(array $objects, string $expand): mixed { - /* Ensure we receive the proper type */ - array_walk($objects, function($obj) { assert($obj instanceof Supertask); }); - - /* Expand requested section */ - switch($expand) { - case 'pretasks': - return $this->getManyToOneRelationViaIntermediate( - $objects, - Supertask::SUPERTASK_ID, - Factory::getSupertaskPretaskFactory(), - SupertaskPretask::SUPERTASK_ID, - Factory::getPretaskFactory(), - Pretask::PRETASK_ID - ); - default: - throw new BadFunctionCallException("Internal error: Expansion '$expand' not implemented!"); - } + public static function getToManyRelationships(): array { + return [ + 'pretasks' => [ + 'key' => Supertask::SUPERTASK_ID, + + 'junctionTableType' => SupertaskPretask::class, + 'junctionTableFilterField' => SupertaskPretask::SUPERTASK_ID, + 'junctionTableJoinField' => SupertaskPretask::PRETASK_ID, + + 'relationType' => Pretask::class, + 'relationKey' => Pretask::PRETASK_ID, + ], + ]; } public function getFormFields(): array { diff --git a/src/inc/apiv2/model/tasks.routes.php b/src/inc/apiv2/model/tasks.routes.php index 4244ac679..978a14dc1 100644 --- a/src/inc/apiv2/model/tasks.routes.php +++ b/src/inc/apiv2/model/tasks.routes.php @@ -23,67 +23,63 @@ public static function getDBAclass(): string { return Task::class; } - public function getExpandables(): array { - return ["assignedAgents", "crackerBinary", "crackerBinaryType", "hashlist", "speeds", "files"]; + public static function getToOneRelationships(): array { + return [ + 'crackerBinary' => [ + 'key' => Task::CRACKER_BINARY_ID, + + 'relationType' => CrackerBinary::class, + 'relationKey' => CrackerBinary::CRACKER_BINARY_ID, + ], + 'crackerBinaryType' => [ + 'key' => Task::CRACKER_BINARY_TYPE_ID, + + 'relationType' => CrackerBinaryType::class, + 'relationKey' => CrackerBinaryType::CRACKER_BINARY_TYPE_ID, + ], + 'hashlist' => [ + 'key' => TaskWrapper::HASHLIST_ID, + + 'relationType' => Hashlist::class, + 'relationKey' => Hashlist::HASHLIST_ID, + + //because task doesnt have a direct connection to hashlist + 'intermediateType' => TaskWrapper::class, + 'joinField' => Task::TASK_WRAPPER_ID, + 'joinFieldRelation' => TaskWrapper::TASK_WRAPPER_ID, + ], + ]; } - protected function fetchExpandObjects(array $objects, string $expand): mixed { - /* Ensure we receive the proper type */ - array_walk($objects, function($obj) { assert($obj instanceof Task); }); - - /* Expand requested section */ - switch($expand) { - case 'assignedAgents': - return $this->getManyToOneRelationViaIntermediate( - $objects, - Task::TASK_ID, - Factory::getAssignmentFactory(), - Assignment::TASK_ID, - Factory::getAgentFactory(), - Agent::AGENT_ID - ); - case 'crackerBinary': - return $this->getForeignKeyRelation( - $objects, - Task::CRACKER_BINARY_ID, - Factory::getCrackerBinaryFactory(), - CrackerBinary::CRACKER_BINARY_ID - ); - case 'crackerBinaryType': - return $this->getForeignKeyRelation( - $objects, - Task::CRACKER_BINARY_TYPE_ID, - Factory::getCrackerBinaryTypeFactory(), - CrackerBinaryType::CRACKER_BINARY_TYPE_ID - ); - case 'hashlist': - return $this->getManyToOneRelationViaIntermediate( - $objects, - Task::TASK_WRAPPER_ID, - Factory::getTaskWrapperFactory(), - TaskWrapper::TASK_WRAPPER_ID, - Factory::getHashlistFactory(), - Hashlist::HASHLIST_ID - ); - case 'speeds': - return $this->getManyToOneRelation( - $objects, - Task::TASK_ID, - Factory::getSpeedFactory(), - Speed::TASK_ID - ); - case 'files': - return $this->getManyToOneRelationViaIntermediate( - $objects, - Task::TASK_ID, - Factory::getFileTaskFactory(), - FileTask::TASK_ID, - Factory::getFileFactory(), - File::FILE_ID - ); - default: - throw new BadFunctionCallException("Internal error: Expansion '$expand' not implemented!"); - } + public static function getToManyRelationships(): array { + return [ + 'assignedAgents' => [ + 'key' => Task::TASK_ID, + + 'junctionTableType' => Assignment::class, + 'junctionTableFilterField' => Assignment::TASK_ID, + 'junctionTableJoinField' => Assignment::AGENT_ID, + + 'relationType' => Agent::class, + 'relationKey' => Agent::AGENT_ID, + ], + 'files' => [ + 'key' => Task::TASK_ID, + + 'junctionTableType' => FileTask::class, + 'junctionTableFilterField' => FileTask::TASK_ID, + 'junctionTableJoinField' => FileTask::FILE_ID, + + 'relationType' => File::class, + 'relationKey' => File::FILE_ID, + ], + 'speeds' => [ + 'key' => Task::TASK_ID, + + 'relationType' => Speed::class, + 'relationKey' => Speed::TASK_ID, + ] + ]; } public function getFormFields(): array { diff --git a/src/inc/apiv2/model/taskwrappers.routes.php b/src/inc/apiv2/model/taskwrappers.routes.php index 9d03f0007..6bd5b7faa 100644 --- a/src/inc/apiv2/model/taskwrappers.routes.php +++ b/src/inc/apiv2/model/taskwrappers.routes.php @@ -11,7 +11,7 @@ require_once(dirname(__FILE__) . "/../common/AbstractModelAPI.class.php"); -class TaskWrappersAPI extends AbstractModelAPI { +class TaskWrapperAPI extends AbstractModelAPI { public static function getBaseUri(): string { return "/api/v2/ui/taskwrappers"; } @@ -24,35 +24,29 @@ public static function getDBAclass(): string { return TaskWrapper::class; } - public function getExpandables(): array { - return ['accessGroup', 'tasks']; - } + public static function getToOneRelationships(): array { + return [ + 'accessGroup' => [ + 'key' => TaskWrapper::ACCESS_GROUP_ID, - protected function fetchExpandObjects(array $objects, string $expand): mixed { - /* Ensure we receive the proper type */ - array_walk($objects, function($obj) { assert($obj instanceof TaskWrapper); }); + 'relationType' => AccessGroup::class, + 'relationKey' => AccessGroup::ACCESS_GROUP_ID, + ], + ]; + } - /* Expand requested section */ - switch($expand) { - case 'accessGroup': - return $this->getForeignKeyRelation( - $objects, - TaskWrapper::ACCESS_GROUP_ID, - Factory::getAccessGroupFactory(), - AccessGroup::ACCESS_GROUP_ID - ); - case 'tasks': - return $this->getManyToOneRelation( - $objects, - TaskWrapper::TASK_WRAPPER_ID, - Factory::getTaskFactory(), - Task::TASK_WRAPPER_ID - ); - default: - throw new BadFunctionCallException("Internal error: Expansion '$expand' not implemented!"); - } + public static function getToManyRelationships(): array { + return [ + 'tasks' => [ + 'key' => TaskWrapper::TASK_WRAPPER_ID, + + 'relationType' => Task::class, + 'relationKey' => Task::TASK_WRAPPER_ID, + ], + ]; } + protected function createObject(array $data): int { assert(False, "TaskWrappers cannot be created via API"); return -1; @@ -104,4 +98,4 @@ protected function deleteObject(object $object): void { } } -TaskWrappersAPI::register($app); \ No newline at end of file +TaskWrapperAPI::register($app); \ No newline at end of file diff --git a/src/inc/apiv2/model/users.routes.php b/src/inc/apiv2/model/users.routes.php index d1214fe5a..2f92db5ef 100644 --- a/src/inc/apiv2/model/users.routes.php +++ b/src/inc/apiv2/model/users.routes.php @@ -20,18 +20,42 @@ public static function getDBAclass(): string { return User::class; } - public function getExpandables(): array { - return ["accessGroups", "globalPermissionGroup"]; + public static function getToOneRelationships(): array { + return [ + 'globalPermissionGroup' => [ + 'key' => User::RIGHT_GROUP_ID, + + 'relationType' => RightGroup::class, + 'relationKey' => RightGroup::RIGHT_GROUP_ID, + ], + ]; + } + + public static function getToManyRelationships(): array { + return [ + 'accessGroups' => [ + 'key' => User::USER_ID, + + 'junctionTableType' => AccessGroupUser::class, + 'junctionTableFilterField' => AccessGroupUser::USER_ID, + 'junctionTableJoinField' => AccessGroupUser::ACCESS_GROUP_ID, + + 'relationType' => AccessGroup::class, + 'relationKey' => AccessGroup::ACCESS_GROUP_ID, + ], + ]; } - protected function fetchExpandObjects(array $objects, string $expand): mixed { - /* Ensure we receive the proper type */ + + + + protected static function fetchExpandObjects(array $objects, string $expand): mixed { array_walk($objects, function($obj) { assert($obj instanceof User); }); /* Expand requested section */ switch($expand) { case 'accessGroups': - return $this->getManyToOneRelationViaIntermediate( + return self::getManyToOneRelationViaIntermediate( $objects, User::USER_ID, Factory::getAccessGroupUserFactory(), @@ -40,7 +64,7 @@ protected function fetchExpandObjects(array $objects, string $expand): mixed { AccessGroup::ACCESS_GROUP_ID ); case 'globalPermissionGroup': - return $this->getForeignKeyRelation( + return self::getForeignKeyRelation( $objects, User::RIGHT_GROUP_ID, Factory::getRightGroupFactory(),