HEX
Server: Apache/2.4.41 (Ubuntu)
System: Linux vmi1674223.contaboserver.net 5.4.0-182-generic #202-Ubuntu SMP Fri Apr 26 12:29:36 UTC 2024 x86_64
User: root (0)
PHP: 7.4.3-4ubuntu2.22
Disabled: pcntl_alarm,pcntl_fork,pcntl_waitpid,pcntl_wait,pcntl_wifexited,pcntl_wifstopped,pcntl_wifsignaled,pcntl_wifcontinued,pcntl_wexitstatus,pcntl_wtermsig,pcntl_wstopsig,pcntl_signal,pcntl_signal_get_handler,pcntl_signal_dispatch,pcntl_get_last_error,pcntl_strerror,pcntl_sigprocmask,pcntl_sigwaitinfo,pcntl_sigtimedwait,pcntl_exec,pcntl_getpriority,pcntl_setpriority,pcntl_async_signals,pcntl_unshare,
Upload Files
File: //opt/openproject/app/services/base_type_service.rb
#-- encoding: UTF-8
#-- copyright
# OpenProject is an open source project management software.
# Copyright (C) 2012-2020 the OpenProject GmbH
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License version 3.
#
# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
# Copyright (C) 2006-2017 Jean-Philippe Lang
# Copyright (C) 2010-2013 the ChiliProject Team
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
#
# See docs/COPYRIGHT.rdoc for more details.
#++

class BaseTypeService
  include Shared::BlockService
  include Contracted

  attr_accessor :contract_class
  attr_accessor :type, :user

  def initialize(type, user)
    self.type = type
    self.user = user
    self.contract_class = ::Types::BaseContract
  end

  def call(params, options, &block)
    result = update(params, options)

    block_with_result(result, &block)
  end

  private

  def update(params, options)
    success = false
    errors = type.errors

    Type.transaction do
      set_scalar_params(params)

      # Only set attribute groups when it exists
      # (Regression #28400)
      unless params[:attribute_groups].nil?
        set_attribute_groups(params)
      end

      set_active_custom_fields

      success, errors = validate_and_save(type, user)
      if success
        after_type_save(params, options)
      else
        raise(ActiveRecord::Rollback)
      end
    end

    ServiceResult.new(success: success,
                      errors: errors,
                      result: type)
  rescue => e
    ServiceResult.new(success: false).tap do |result|
      result.errors.add(:base, e.message)
    end
  end

  def set_scalar_params(params)
    type.attributes = params.except(:attribute_groups)
  end

  def set_attribute_groups(params)
    if params[:attribute_groups].empty?
      type.reset_attribute_groups
    else
      type.attribute_groups = parse_attribute_groups_params(params)
    end
  end

  def parse_attribute_groups_params(params)
    return if params[:attribute_groups].nil?

    transform_attribute_groups(params[:attribute_groups])
  end

  def after_type_save(_params, _options)
    # noop to be overwritten by subclasses
  end

  def transform_attribute_groups(groups)
    groups.map do |group|
      if group['type'] == 'query'
        transform_query_group(group)
      else
        transform_attribute_group(group)
      end
    end
  end

  def transform_attribute_group(group)
    name =
      if group['key']
        group['key'].to_sym
      else
        group['name']
      end

    [
      name,
      group['attributes'].map { |attr| attr['key'] }
    ]
  end

  def transform_query_group(group)
    name = group['name']
    props = JSON.parse group['query']

    query = Query.new_default(name: "Embedded table: #{name}")

    ::API::V3::UpdateQueryFromV3ParamsService
      .new(query, user)
      .call(props.with_indifferent_access)

    query.show_hierarchies = false
    query.hidden = true

    [
      name,
      [query]
    ]
  end

  ##
  # Syncs attribute group settings for custom fields with enabled custom fields
  # for this type. If a custom field is not in a group, it is removed from the
  # custom_field_ids list.
  def set_active_custom_fields
    active_cf_ids = []

    type.attribute_groups.each do |group|
      group.members.each do |attribute|
        if CustomField.custom_field_attribute? attribute
          active_cf_ids << attribute.gsub(/^custom_field_/, '').to_i
        end
      end
    end

    type.custom_field_ids = active_cf_ids.uniq
  end
end