parent
cd049721d5
commit
399e8c3ed9
@ -1,52 +0,0 @@ |
||||
#-- 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. |
||||
#++ |
||||
|
||||
module ExtendedHTTP |
||||
# Use this in response to an HTTP POST (or PUT), telling the client where the |
||||
# new resource is. Works just like redirect_to, but sends back a 303 (See |
||||
# Other) status code. Redirects should be used to tell the client to repeat |
||||
# the same request on a different resource, and see_other when we want the |
||||
# client to follow a POST (on this resource) with a GET (to the new resource). |
||||
# |
||||
# This is especially useful for successful create actions. |
||||
def see_other(options = {}) |
||||
if options.is_a?(Hash) |
||||
redirect_to options.merge(status: :see_other) |
||||
else |
||||
redirect_to options, status: :see_other |
||||
end |
||||
end |
||||
|
||||
# Use this in response to an HTTP PUT (or POST), telling the client that |
||||
# everything went well and the desired change was performed successfully. |
||||
# |
||||
# This is especially useful for successful update actions. |
||||
def no_content |
||||
render body: '', status: :no_content |
||||
end |
||||
end |
@ -0,0 +1,8 @@ |
||||
module OpenProject |
||||
module Patches |
||||
module Array |
||||
end |
||||
end |
||||
end |
||||
|
||||
Array.send(:include, Redmine::Diff::Diffable) |
@ -1,30 +0,0 @@ |
||||
#-- 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. |
||||
#++ |
||||
|
||||
Dir[File.dirname(__FILE__) + '/core_ext/*.rb'].each { |file| require(file) } |
@ -1,36 +0,0 @@ |
||||
#-- 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. |
||||
#++ |
||||
|
||||
require File.dirname(__FILE__) + '/string/conversions' |
||||
require File.dirname(__FILE__) + '/string/inflections' |
||||
|
||||
class String #:nodoc: |
||||
include Redmine::CoreExtensions::String::Conversions |
||||
include Redmine::CoreExtensions::String::Inflections |
||||
end |
@ -0,0 +1,179 @@ |
||||
# = Diff |
||||
# (({diff.rb})) - computes the differences between two arrays or |
||||
# strings. Copyright (C) 2001 Lars Christensen |
||||
# |
||||
# == Synopsis |
||||
# |
||||
# diff = Diff.new(a, b) |
||||
# b = a.patch(diff) |
||||
# |
||||
# == Class Diff |
||||
# === Class Methods |
||||
# --- Diff.new(a, b) |
||||
# --- a.diff(b) |
||||
# Creates a Diff object which represent the differences between |
||||
# ((|a|)) and ((|b|)). ((|a|)) and ((|b|)) can be either be arrays |
||||
# of any objects, strings, or object of any class that include |
||||
# module ((|Diffable|)) |
||||
# |
||||
# == Module Diffable |
||||
# The module ((|Diffable|)) is intended to be included in any class for |
||||
# which differences are to be computed. Diffable is included into String |
||||
# and Array when (({diff.rb})) is (({require}))'d. |
||||
# |
||||
# Classes including Diffable should implement (({[]})) to get element at |
||||
# integer indices, (({<<})) to append elements to the object and |
||||
# (({ClassName#new})) should accept 0 arguments to create a new empty |
||||
# object. |
||||
# |
||||
# === Instance Methods |
||||
# --- Diffable#patch(diff) |
||||
# Applies the differences from ((|diff|)) to the object ((|obj|)) |
||||
# and return the result. ((|obj|)) is not changed. ((|obj|)) and |
||||
# can be either an array or a string, but must match the object |
||||
# from which the ((|diff|)) was created. |
||||
|
||||
class Redmine::Diff::ArrayStringDiff |
||||
VERSION = 0.3 |
||||
|
||||
def self.lcs(a, b) |
||||
astart = 0 |
||||
bstart = 0 |
||||
afinish = a.length - 1 |
||||
bfinish = b.length - 1 |
||||
mvector = [] |
||||
|
||||
# First we prune off any common elements at the beginning |
||||
while astart <= afinish && bstart <= afinish && a[astart] == b[bstart] |
||||
mvector[astart] = bstart |
||||
astart += 1 |
||||
bstart += 1 |
||||
end |
||||
|
||||
# now the end |
||||
while astart <= afinish && bstart <= bfinish && a[afinish] == b[bfinish] |
||||
mvector[afinish] = bfinish |
||||
afinish -= 1 |
||||
bfinish -= 1 |
||||
end |
||||
|
||||
bmatches = b.reverse_hash(bstart..bfinish) |
||||
thresh = [] |
||||
links = [] |
||||
|
||||
(astart..afinish).each do |aindex| |
||||
aelem = a[aindex] |
||||
next unless bmatches.has_key? aelem |
||||
k = nil |
||||
bmatches[aelem].reverse_each { |bindex| |
||||
if k && (thresh[k] > bindex) && (thresh[k - 1] < bindex) |
||||
thresh[k] = bindex |
||||
else |
||||
k = thresh.replacenextlarger(bindex, k) |
||||
end |
||||
links[k] = [(k == 0) ? nil : links[k - 1], aindex, bindex] if k |
||||
} |
||||
end |
||||
|
||||
if !thresh.empty? |
||||
link = links[thresh.length - 1] |
||||
while link |
||||
mvector[link[1]] = link[2] |
||||
link = link[0] |
||||
end |
||||
end |
||||
|
||||
mvector |
||||
end |
||||
|
||||
def makediff(a, b) |
||||
mvector = self.class.lcs(a, b) |
||||
ai = bi = 0 |
||||
while ai < mvector.length |
||||
bline = mvector[ai] |
||||
if bline |
||||
while bi < bline |
||||
discardb(bi, b[bi]) |
||||
bi += 1 |
||||
end |
||||
match(ai, bi) |
||||
bi += 1 |
||||
else |
||||
discarda(ai, a[ai]) |
||||
end |
||||
ai += 1 |
||||
end |
||||
while ai < a.length |
||||
discarda(ai, a[ai]) |
||||
ai += 1 |
||||
end |
||||
while bi < b.length |
||||
discardb(bi, b[bi]) |
||||
bi += 1 |
||||
end |
||||
match(ai, bi) |
||||
1 |
||||
end |
||||
|
||||
def compactdiffs |
||||
diffs = [] |
||||
@diffs.each do |df| |
||||
i = 0 |
||||
curdiff = [] |
||||
while i < df.length |
||||
whot = df[i][0] |
||||
s = @isstring ? df[i][2].chr : [df[i][2]] |
||||
p = df[i][1] |
||||
last = df[i][1] |
||||
i += 1 |
||||
while df[i] && df[i][0] == whot && df[i][1] == last + 1 |
||||
s << df[i][2] |
||||
last = df[i][1] |
||||
i += 1 |
||||
end |
||||
curdiff.push [whot, p, s] |
||||
end |
||||
diffs.push curdiff |
||||
end |
||||
diffs |
||||
end |
||||
|
||||
attr_reader :diffs, :difftype |
||||
|
||||
def initialize(diffs_or_a, b = nil, isstring = nil) |
||||
if b.nil? |
||||
@diffs = diffs_or_a |
||||
@isstring = isstring |
||||
else |
||||
@diffs = [] |
||||
@curdiffs = [] |
||||
makediff(diffs_or_a, b) |
||||
@difftype = diffs_or_a.class |
||||
end |
||||
end |
||||
|
||||
def match(_ai, _bi) |
||||
@diffs.push @curdiffs unless @curdiffs.empty? |
||||
@curdiffs = [] |
||||
end |
||||
|
||||
def discarda(i, elem) |
||||
@curdiffs.push ['-', i, elem] |
||||
end |
||||
|
||||
def discardb(i, elem) |
||||
@curdiffs.push ['+', i, elem] |
||||
end |
||||
|
||||
def compact |
||||
Diff.new(compactdiffs) |
||||
end |
||||
|
||||
def compact! |
||||
@diffs = compactdiffs |
||||
end |
||||
|
||||
def inspect |
||||
@diffs.inspect |
||||
end |
||||
end |
@ -0,0 +1,123 @@ |
||||
# = Diff |
||||
# (({diff.rb})) - computes the differences between two arrays or |
||||
# strings. Copyright (C) 2001 Lars Christensen |
||||
# |
||||
# == Synopsis |
||||
# |
||||
# diff = Diff.new(a, b) |
||||
# b = a.patch(diff) |
||||
# |
||||
# == Class Diff |
||||
# === Class Methods |
||||
# --- Diff.new(a, b) |
||||
# --- a.diff(b) |
||||
# Creates a Diff object which represent the differences between |
||||
# ((|a|)) and ((|b|)). ((|a|)) and ((|b|)) can be either be arrays |
||||
# of any objects, strings, or object of any class that include |
||||
# module ((|Diffable|)) |
||||
# |
||||
# == Module Diffable |
||||
# The module ((|Diffable|)) is intended to be included in any class for |
||||
# which differences are to be computed. Diffable is included into String |
||||
# and Array when (({diff.rb})) is (({require}))'d. |
||||
# |
||||
# Classes including Diffable should implement (({[]})) to get element at |
||||
# integer indices, (({<<})) to append elements to the object and |
||||
# (({ClassName#new})) should accept 0 arguments to create a new empty |
||||
# object. |
||||
# |
||||
# === Instance Methods |
||||
# --- Diffable#patch(diff) |
||||
# Applies the differences from ((|diff|)) to the object ((|obj|)) |
||||
# and return the result. ((|obj|)) is not changed. ((|obj|)) and |
||||
# can be either an array or a string, but must match the object |
||||
# from which the ((|diff|)) was created. |
||||
|
||||
module Redmine::Diff::Diffable |
||||
def diff(b) |
||||
Redmine::Diff::ArrayStringDiff.new(self, b) |
||||
end |
||||
|
||||
# Create a hash that maps elements of the array to arrays of indices |
||||
# where the elements are found. |
||||
|
||||
def reverse_hash(range = (0...length)) |
||||
revmap = {} |
||||
range.each do |i| |
||||
elem = self[i] |
||||
if revmap.has_key? elem |
||||
revmap[elem].push i |
||||
else |
||||
revmap[elem] = [i] |
||||
end |
||||
end |
||||
revmap |
||||
end |
||||
|
||||
def replacenextlarger(value, high = nil) |
||||
high ||= length |
||||
if self.empty? || value > self[-1] |
||||
push value |
||||
return high |
||||
end |
||||
# binary search for replacement point |
||||
low = 0 |
||||
while low < high |
||||
index = (high + low) / 2 |
||||
found = self[index] |
||||
return nil if value == found |
||||
if value > found |
||||
low = index + 1 |
||||
else |
||||
high = index |
||||
end |
||||
end |
||||
|
||||
self[low] = value |
||||
# $stderr << "replace #{value} : 0/#{low}/#{init_high} (#{steps} steps) (#{init_high-low} off )\n" |
||||
# $stderr.puts self.inspect |
||||
# gets |
||||
# p length - low |
||||
low |
||||
end |
||||
|
||||
def patch(diff) |
||||
newary = nil |
||||
if diff.difftype == String |
||||
newary = diff.difftype.new('') |
||||
else |
||||
newary = diff.difftype.new |
||||
end |
||||
ai = 0 |
||||
bi = 0 |
||||
diff.diffs.each do |d| |
||||
d.each { |mod| |
||||
case mod[0] |
||||
when '-' |
||||
while ai < mod[1] |
||||
newary << self[ai] |
||||
ai += 1 |
||||
bi += 1 |
||||
end |
||||
ai += 1 |
||||
when '+' |
||||
while bi < mod[1] |
||||
newary << self[ai] |
||||
ai += 1 |
||||
bi += 1 |
||||
end |
||||
newary << mod[2] |
||||
bi += 1 |
||||
else |
||||
raise 'Unknown diff action' |
||||
end |
||||
} |
||||
end |
||||
while ai < length |
||||
newary << self[ai] |
||||
ai += 1 |
||||
bi += 1 |
||||
end |
||||
newary |
||||
end |
||||
end |
@ -1,33 +0,0 @@ |
||||
#-- 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. |
||||
#++ |
||||
|
||||
module Redmine |
||||
# THIS IS A REDMINE COMPATIBILITY INTERFACE |
||||
VERSION = OpenProject::VERSION |
||||
end |
@ -0,0 +1,23 @@ |
||||
module BasicData |
||||
module Backlogs |
||||
module TypeSeeder |
||||
def coded_visibility_table |
||||
super.merge backlogs_visibility_table |
||||
end |
||||
|
||||
## |
||||
# Relies on type names in the core TypeSeeder being (in this order) |
||||
# task, milestone, phase, feature, epic, user_story, bug |
||||
# and 0 to 2 being mapped to |
||||
# hidden, default, visible |
||||
def backlogs_visibility_table |
||||
{ |
||||
story_points: [0, 0, 0, 1, 2, 2, 1], |
||||
remaining_time: [1, 0, 0, 1, 1, 1, 1] |
||||
} |
||||
end |
||||
end |
||||
|
||||
BasicData::TypeSeeder.prepend BasicData::Backlogs::TypeSeeder |
||||
end |
||||
end |
@ -1,25 +0,0 @@ |
||||
module StandardSeeder |
||||
module BasicData |
||||
module Backlogs |
||||
module TypeSeeder |
||||
def coded_visibility_table |
||||
super.merge backlogs_visibility_table |
||||
end |
||||
|
||||
## |
||||
# Relies on type names in the core TypeSeeder being (in this order) |
||||
# task, milestone, phase, feature, epic, user_story, bug |
||||
# and 0 to 2 being mapped to |
||||
# hidden, default, visible |
||||
def backlogs_visibility_table |
||||
{ |
||||
story_points: [0, 0, 0, 1, 2, 2, 1], |
||||
remaining_time: [1, 0, 0, 1, 1, 1, 1] |
||||
} |
||||
end |
||||
end |
||||
|
||||
StandardSeeder::BasicData::TypeSeeder.prepend StandardSeeder::BasicData::Backlogs::TypeSeeder |
||||
end |
||||
end |
||||
end |
@ -0,0 +1,105 @@ |
||||
module DemoData |
||||
class OverviewSeeder < Seeder |
||||
include ::DemoData::References |
||||
|
||||
def seed_data! |
||||
puts "*** Seeding Overview" |
||||
|
||||
Array(demo_data_for('projects')).each do |(_key, project_config)| |
||||
next unless overview_config(project_config) |
||||
|
||||
puts " -Creating overview for #{project_config[:name]}" |
||||
|
||||
overview = overview_from_config(project_config) |
||||
|
||||
overview_config(project_config)[:widgets].each do |widget_config| |
||||
build_widget(overview, widget_config) |
||||
end |
||||
|
||||
overview.save! |
||||
end |
||||
|
||||
add_permission |
||||
end |
||||
|
||||
def applicable? |
||||
Grids::Overview.count.zero? && demo_projects_exist? |
||||
end |
||||
|
||||
private |
||||
|
||||
def demo_projects_exist? |
||||
identifiers = Array(demo_data_for('projects')) |
||||
.map { |_key, project| project[:identifier] } |
||||
|
||||
identifiers |
||||
.all? { |ident| Project.where(identifier: ident).exists? } |
||||
end |
||||
|
||||
def build_widget(overview, widget_config) |
||||
create_attachments!(overview, widget_config) |
||||
|
||||
widget_options = widget_config[:options] |
||||
|
||||
text_with_references(overview, widget_options) |
||||
query_id_references(overview, widget_options) |
||||
|
||||
overview.widgets.build(widget_config.except(:attachments)) |
||||
end |
||||
|
||||
def create_attachments!(overview, attributes) |
||||
Array(attributes[:attachments]).each do |file_name| |
||||
attachment = overview.attachments.build |
||||
attachment.author = User.admin.first |
||||
attachment.file = File.new attachment_path(file_name) |
||||
|
||||
attachment.save! |
||||
end |
||||
end |
||||
|
||||
def attachment_path(file_name) |
||||
::Overviews::Engine.root.join( |
||||
"config/locales/media/#{I18n.locale}/#{file_name}" |
||||
) |
||||
end |
||||
|
||||
def project_from_config(config) |
||||
Project.find_by! identifier: config[:identifier] |
||||
end |
||||
|
||||
def overview_from_config(project_config) |
||||
params = overview_config(project_config) |
||||
.slice(:row_count, :column_count) |
||||
.merge(project: project_from_config(project_config)) |
||||
|
||||
Grids::Overview |
||||
.create(params) |
||||
end |
||||
|
||||
def overview_config(project_config) |
||||
project_config[:"project-overview"] |
||||
end |
||||
|
||||
def text_with_references(overview, widget_options) |
||||
if widget_options && widget_options[:text] |
||||
widget_options[:text] = with_references(widget_options[:text], overview.project) |
||||
widget_options[:text] = link_attachments(widget_options[:text], overview.attachments) |
||||
end |
||||
end |
||||
|
||||
def query_id_references(overview, widget_options) |
||||
if widget_options && widget_options[:queryId] |
||||
widget_options[:queryId] = with_references(widget_options[:queryId], overview.project) |
||||
end |
||||
end |
||||
|
||||
def add_permission |
||||
Role |
||||
.includes(:role_permissions) |
||||
.where(role_permissions: { permission: 'edit_project' }) |
||||
.each do |role| |
||||
role.add_permission!(:manage_overview) |
||||
end |
||||
end |
||||
end |
||||
end |
@ -1,107 +0,0 @@ |
||||
module DemoData |
||||
module Overviews |
||||
class OverviewSeeder < Seeder |
||||
include ::DemoData::References |
||||
|
||||
def seed_data! |
||||
puts "*** Seeding Overview" |
||||
|
||||
Array(demo_data_for('projects')).each do |(_key, project_config)| |
||||
next unless overview_config(project_config) |
||||
|
||||
puts " -Creating overview for #{project_config[:name]}" |
||||
|
||||
overview = overview_from_config(project_config) |
||||
|
||||
overview_config(project_config)[:widgets].each do |widget_config| |
||||
build_widget(overview, widget_config) |
||||
end |
||||
|
||||
overview.save! |
||||
end |
||||
|
||||
add_permission |
||||
end |
||||
|
||||
def applicable? |
||||
Grids::Overview.count.zero? && demo_projects_exist? |
||||
end |
||||
|
||||
private |
||||
|
||||
def demo_projects_exist? |
||||
identifiers = Array(demo_data_for('projects')) |
||||
.map { |_key, project| project[:identifier] } |
||||
|
||||
identifiers |
||||
.all? { |ident| Project.where(identifier: ident).exists? } |
||||
end |
||||
|
||||
def build_widget(overview, widget_config) |
||||
create_attachments!(overview, widget_config) |
||||
|
||||
widget_options = widget_config[:options] |
||||
|
||||
text_with_references(overview, widget_options) |
||||
query_id_references(overview, widget_options) |
||||
|
||||
overview.widgets.build(widget_config.except(:attachments)) |
||||
end |
||||
|
||||
def create_attachments!(overview, attributes) |
||||
Array(attributes[:attachments]).each do |file_name| |
||||
attachment = overview.attachments.build |
||||
attachment.author = User.admin.first |
||||
attachment.file = File.new attachment_path(file_name) |
||||
|
||||
attachment.save! |
||||
end |
||||
end |
||||
|
||||
def attachment_path(file_name) |
||||
::Overviews::Engine.root.join( |
||||
"config/locales/media/#{I18n.locale}/#{file_name}" |
||||
) |
||||
end |
||||
|
||||
def project_from_config(config) |
||||
Project.find_by! identifier: config[:identifier] |
||||
end |
||||
|
||||
def overview_from_config(project_config) |
||||
params = overview_config(project_config) |
||||
.slice(:row_count, :column_count) |
||||
.merge(project: project_from_config(project_config)) |
||||
|
||||
Grids::Overview |
||||
.create(params) |
||||
end |
||||
|
||||
def overview_config(project_config) |
||||
project_config[:"project-overview"] |
||||
end |
||||
|
||||
def text_with_references(overview, widget_options) |
||||
if widget_options && widget_options[:text] |
||||
widget_options[:text] = with_references(widget_options[:text], overview.project) |
||||
widget_options[:text] = link_attachments(widget_options[:text], overview.attachments) |
||||
end |
||||
end |
||||
|
||||
def query_id_references(overview, widget_options) |
||||
if widget_options && widget_options[:queryId] |
||||
widget_options[:queryId] = with_references(widget_options[:queryId], overview.project) |
||||
end |
||||
end |
||||
|
||||
def add_permission |
||||
Role |
||||
.includes(:role_permissions) |
||||
.where(role_permissions: { permission: 'edit_project' }) |
||||
.each do |role| |
||||
role.add_permission!(:manage_overview) |
||||
end |
||||
end |
||||
end |
||||
end |
||||
end |
@ -1,5 +1,7 @@ |
||||
module WorkPackageDisplay |
||||
def display_id |
||||
"#{(kind.is_standard) ? "" : "#{kind.name}"} ##{id}" |
||||
module OpenProject::PDFExport::ExportCard::ModelDisplay |
||||
module WorkPackageDisplay |
||||
def display_id |
||||
"#{kind.is_standard ? "" : "#{kind.name}"} ##{id}" |
||||
end |
||||
end |
||||
end |
||||
|
@ -1,5 +0,0 @@ |
||||
module OpenProject |
||||
module Recaptcha |
||||
require "open_project/recaptcha/engine" |
||||
end |
||||
end |
@ -1,95 +0,0 @@ |
||||
module TwoFactorAuthentication |
||||
class TokenService |
||||
attr_reader :user, :device, :strategy, :channel |
||||
|
||||
## |
||||
# Create a token service for the given user. |
||||
def initialize(user:, use_device: nil, use_channel: nil) |
||||
@user = user |
||||
@device = use_device || user.otp_devices.get_default |
||||
@channel = use_channel || device.try(:channel) |
||||
|
||||
matching_strategy = get_matching_strategy |
||||
if matching_strategy |
||||
@strategy = matching_strategy.new(user: @user, device: @device) |
||||
end |
||||
end |
||||
|
||||
## |
||||
# Determines whether a token should be entered by the user. |
||||
def requires_token? |
||||
# If 2FA is enforced, always required |
||||
return true if manager.enforced? |
||||
|
||||
# Otherwise, only enabled if active and a device is present for the user |
||||
return manager.enabled? && device.present? |
||||
end |
||||
|
||||
## |
||||
# Request a token through the active strategy |
||||
# IF the instance is set up to have optional 2FA |
||||
def request |
||||
# Validate that we can request the token for this user |
||||
# and get the matching strategy we will use |
||||
verify_device_and_strategy |
||||
|
||||
# Produce the token with the given strategy (e.g., sending an sms) |
||||
strategy.transmit |
||||
|
||||
ServiceResult.new(success: true, result: strategy.transmit_success_message) |
||||
rescue => e |
||||
Rails.logger.error "[2FA plugin] Error during token request to user##{user.id}: #{e}" |
||||
|
||||
result = ServiceResult.new(success: false) |
||||
result.errors.add(:base, e.message) |
||||
|
||||
result |
||||
end |
||||
|
||||
## |
||||
# Validate a token that was input by the user |
||||
def verify(input_token) |
||||
# Validate that we can request the token for this user |
||||
# and get the matching strategy we will use |
||||
verify_device_and_strategy |
||||
|
||||
# Produce the token with the given strategy (e.g., sending an sms) |
||||
result = strategy.verify input_token |
||||
|
||||
ServiceResult.new(success: result) |
||||
rescue => e |
||||
Rails.logger.error "[2FA plugin] Error during token validation for user##{user.id}: #{e}" |
||||
|
||||
result = ServiceResult.new(success: false) |
||||
result.errors.add(:base, e.message) |
||||
|
||||
result |
||||
end |
||||
|
||||
private |
||||
|
||||
## |
||||
# Get the matching strategy from the desired channel, if set. |
||||
def get_matching_strategy |
||||
if @channel |
||||
manager.find_matching_strategy(@channel) |
||||
end |
||||
end |
||||
|
||||
## |
||||
# Perform service checks for the request and validate endpoints of this service |
||||
def verify_device_and_strategy |
||||
raise I18n.t('two_factor_authentication.error_2fa_disabled') unless manager.enabled? |
||||
|
||||
# Ensure the user's default device for OTP exists |
||||
raise I18n.t('two_factor_authentication.error_no_device') if device.nil? |
||||
|
||||
# Ensure a matching registered strategy for the device's channel exists |
||||
raise I18n.t('two_factor_authentication.error_no_matching_strategy') if strategy.nil? |
||||
end |
||||
|
||||
def manager |
||||
::OpenProject::TwoFactorAuthentication::TokenStrategyManager |
||||
end |
||||
end |
||||
end |
@ -1,5 +0,0 @@ |
||||
module OpenProject |
||||
module TwoFactorAuthentication |
||||
require "open_project/two_factor_authentication/engine" |
||||
end |
||||
end |
Loading…
Reference in new issue