knjtasks 0.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (69) hide show
  1. data/.document +5 -0
  2. data/.rspec +1 -0
  3. data/Gemfile +18 -0
  4. data/Gemfile.lock +66 -0
  5. data/LICENSE.txt +20 -0
  6. data/README.rdoc +19 -0
  7. data/Rakefile +49 -0
  8. data/VERSION +1 -0
  9. data/files/database_schema.rb +174 -0
  10. data/lib/knjtasks.rb +172 -0
  11. data/locales/da_DK/LC_MESSAGES/default.mo +0 -0
  12. data/locales/da_DK/LC_MESSAGES/default.po +1153 -0
  13. data/locales/da_DK/title.txt +1 -0
  14. data/locales/en_GB/title.txt +1 -0
  15. data/models/class_comment.rb +44 -0
  16. data/models/class_customer.rb +13 -0
  17. data/models/class_email_check.rb +7 -0
  18. data/models/class_project.rb +22 -0
  19. data/models/class_task.rb +163 -0
  20. data/models/class_task_assigned_user.rb +62 -0
  21. data/models/class_task_check.rb +26 -0
  22. data/models/class_timelog.rb +82 -0
  23. data/models/class_user.rb +125 -0
  24. data/models/class_user_project_link.rb +66 -0
  25. data/models/class_user_rank.rb +3 -0
  26. data/models/class_user_rank_link.rb +17 -0
  27. data/models/class_user_task_list_link.rb +17 -0
  28. data/pages/admin.rhtml +7 -0
  29. data/pages/comment_edit.rhtml +121 -0
  30. data/pages/comment_update_id_per_obj.rhtml +41 -0
  31. data/pages/customer_edit.rhtml +69 -0
  32. data/pages/customer_search.rhtml +80 -0
  33. data/pages/customer_show.rhtml +50 -0
  34. data/pages/frontpage.rhtml +198 -0
  35. data/pages/project_edit.rhtml +129 -0
  36. data/pages/project_search.rhtml +82 -0
  37. data/pages/project_show.rhtml +203 -0
  38. data/pages/task_check_edit.rhtml +98 -0
  39. data/pages/task_edit.rhtml +168 -0
  40. data/pages/task_search.rhtml +131 -0
  41. data/pages/task_show.rhtml +454 -0
  42. data/pages/timelog_edit.rhtml +134 -0
  43. data/pages/timelog_search.rhtml +318 -0
  44. data/pages/user_edit.rhtml +223 -0
  45. data/pages/user_login.rhtml +83 -0
  46. data/pages/user_profile.rhtml +89 -0
  47. data/pages/user_rank_search.rhtml +95 -0
  48. data/pages/user_search.rhtml +136 -0
  49. data/pages/user_show.rhtml +87 -0
  50. data/pages/workstatus.rhtml +320 -0
  51. data/scripts/fckeditor_validate_login.rb +23 -0
  52. data/spec/knjtasks_spec.rb +115 -0
  53. data/spec/spec_helper.rb +12 -0
  54. data/threads/thread_mail_task_comments.rb +114 -0
  55. data/www/api/task.rhtml +9 -0
  56. data/www/api/user.rhtml +20 -0
  57. data/www/clean.rhtml +14 -0
  58. data/www/css/default.css +186 -0
  59. data/www/gfx/body_bg.jpg +0 -0
  60. data/www/gfx/button_bg.png +0 -0
  61. data/www/gfx/main_box_design.png +0 -0
  62. data/www/gfx/main_box_left.png +0 -0
  63. data/www/gfx/main_box_right.png +0 -0
  64. data/www/gfx/main_box_top.png +0 -0
  65. data/www/gfx/main_box_top_left.png +0 -0
  66. data/www/gfx/main_box_top_right.png +0 -0
  67. data/www/index.rhtml +154 -0
  68. data/www/js/default.js +112 -0
  69. metadata +208 -0
@@ -0,0 +1 @@
1
+ Danish
@@ -0,0 +1 @@
1
+ English
@@ -0,0 +1,44 @@
1
+ class Knjtasks::Comment < Knj::Datarow
2
+ has_one [
3
+ {:class => :User, :required => true}
4
+ ]
5
+
6
+ def self.list(d)
7
+ sql = "SELECT * FROM Comment WHERE 1=1"
8
+
9
+ ret = list_helper(d)
10
+ d.args.each do |key, val|
11
+ case key
12
+ when "object_lookup"
13
+ sql += " AND object_class = '#{val.table}' AND object_id = '#{d.db.esc(val.id)}'"
14
+ else
15
+ raise sprintf(_("Invalid key: %s."), key)
16
+ end
17
+ end
18
+
19
+ sql += ret[:sql_where]
20
+ sql += ret[:sql_order]
21
+ sql += ret[:sql_limit]
22
+
23
+ return d.ob.list_bysql(:Comment, sql)
24
+ end
25
+
26
+ def self.add(d)
27
+ d.data[:user_id ] = _site.user.id if !d.data[:user_id] and _site.user
28
+ d.data[:date_saved] = Time.new if !d.data[:date_saved]
29
+
30
+ raise "No 'object_class' was given." if !d.data[:object_class]
31
+ raise "No 'object_id' was given." if !d.data[:object_id]
32
+
33
+ obj = d.ob.get(d.data[:object_class], d.data[:object_id])
34
+ user = d.ob.get(:User, d.data[:user_id])
35
+ end
36
+
37
+ def object
38
+ return ob.get_try(self, :object_id, self[:object_class])
39
+ end
40
+
41
+ def add_after(d)
42
+ self.object.send_notify_new_comment(self) if self.object.class.name == "Knjtasks::Task"
43
+ end
44
+ end
@@ -0,0 +1,13 @@
1
+ class Knjtasks::Customer < Knj::Datarow
2
+ has_many [
3
+ {:class => :Project, :col => :customer_id, :depends => true}
4
+ ]
5
+
6
+ def self.add(d)
7
+ d.data[:date_added] = Time.new if !d.data[:date_added]
8
+ end
9
+
10
+ def html
11
+ return "<a href=\"?show=customer_show&amp;customer_id=#{id}\">#{name.html}</a>"
12
+ end
13
+ end
@@ -0,0 +1,7 @@
1
+ class Knjtasks::Email_check < Knj::Datarow
2
+ def self.checked_id?(d, email_id_str)
3
+ data = d.db.query("SELECT id FROM Email_check WHERE email_id_str = '#{d.db.esc(email_id_str)}' LIMIT 1").fetch
4
+ return true if data
5
+ return false
6
+ end
7
+ end
@@ -0,0 +1,22 @@
1
+ class Knjtasks::Project < Knj::Datarow
2
+ has_one [
3
+ {:class => :User, :col => :added_user_id, :method => :added_user, :required => true},
4
+ {:class => :Customer, :col => :customer_id, :method => :customer, :required => true}
5
+ ]
6
+
7
+ has_many [
8
+ {:class => :Task, :col => :project_id, :depends => true},
9
+ {:class => :User_project_link, :col => :project_id, :method => :users, :autodelete => true, :depends => true}
10
+ ]
11
+
12
+ def self.add(d)
13
+ raise _("Invalid name given.") if d.data[:name].to_s.strip.length <= 0
14
+
15
+ d.data[:added_user_id] = _site.user.id if !d.data[:added_user_id] and _site.user
16
+ d.data[:added_date] = Time.new if !d.data[:added_date]
17
+ end
18
+
19
+ def html
20
+ return "<a href=\"?show=project_show&amp;project_id=#{id}\">#{name.html}</a>"
21
+ end
22
+ end
@@ -0,0 +1,163 @@
1
+ class Knjtasks::Task < Knj::Datarow
2
+ has_many [
3
+ [:Timelog, :task_id],
4
+ [:Task_assigned_user, :task_id, :assigned_users],
5
+ [:Task_check, :task_id, :checks],
6
+ [:User_task_list_link, :task_id, :user_lists]
7
+ ]
8
+ has_one [
9
+ {:class => :Project, :required => true},
10
+ :User
11
+ ]
12
+
13
+ def initialize(*args, &blk)
14
+ super(*args, &blk)
15
+ self[:priority] = 1 if self[:priority].to_i <= 0 or self[:priority].to_i > 10
16
+ end
17
+
18
+ def self.add(d)
19
+ raise _("No project-ID was given.") if d.data[:project_id].to_i <= 0
20
+ raise _("Invalid name was given.") if d.data[:name].to_s.strip.length <= 0
21
+
22
+ d.data[:user_id] = _site.user.id if _site.user
23
+ d.data[:date_added] = Time.now if !d.data[:date_added]
24
+ d.data[:priority] = 1 if d.data[:priority].to_i <= 0 or d.data[:priority].to_i > 10
25
+
26
+ begin
27
+ task = d.ob.get(:Project, d.data[:project_id])
28
+ rescue Errno::ENOENT
29
+ raise sprintf(_("A project with the given project-ID could not be found: '%s'."), d.data[:project_id])
30
+ end
31
+ end
32
+
33
+ def self.type_opts(d)
34
+ return {
35
+ :feature => _("Feature"),
36
+ :bug => _("Bug report"),
37
+ :question => _("Question")
38
+ }
39
+ end
40
+
41
+ def self.status_opts(d)
42
+ return {
43
+ :open => _("Open"),
44
+ :confirmed => _("Confirmed"),
45
+ :waiting => _("Waiting"),
46
+ :closed => _("Closed")
47
+ }
48
+ end
49
+
50
+ #Sends a notification about a newly added comment to a task.
51
+ def send_notify_new_comment(comment)
52
+ self.notify_emails.each do |data|
53
+ subj = "#{sprintf(_hb.gettext.gettext("Task #%1$s: %2$s", data[:user].locale), self.id, self.name)} - #{sprintf(_hb.gettext.gettext("New comment from: %s", data[:user].locale), comment.user.name)}"
54
+
55
+ if !self.user
56
+ user_html = "[#{_hb.gettext.gettext("no user", data[:user].locale)}]"
57
+ else
58
+ user_html = comment.user.name.html
59
+ end
60
+
61
+ html = ""
62
+
63
+ html += sprintf(_hb.gettext.gettext("A new comment has been written to the task '%s'.", data[:user].locale), self.name)
64
+ html += "<br /><br />"
65
+
66
+ html += "<b>#{_hb.gettext.gettext("Author", data[:user].locale)}:</b><br />"
67
+ html += "#{user_html}<br /><br />"
68
+
69
+ html += "<b>#{_hb.gettext.gettext("Task", data[:user].locale)}:</b><br />"
70
+ html += "<a href=\"#{url}\">#{url}</a><br /><br />"
71
+
72
+ html += "<b>#{_hb.gettext.gettext("Comment", data[:user].locale)}</b><br />"
73
+ html += comment[:comment]
74
+
75
+ _hb.mail(:to => data[:email], :subject => subj, :html => html)
76
+ end
77
+ end
78
+
79
+ #Sends a
80
+ def send_notify_assigned(user_assigned, user_by)
81
+ return false if user_assigned[:email].to_s.strip.length <= 0
82
+
83
+ subj = sprintf(_hb.gettext.gettext("You have been assigned to: %s", user_assigned.locale), self.name)
84
+
85
+ html = ""
86
+
87
+ html += "<b>#{_hb.gettext.gettext("Assigned by", user_assigned.locale)}:</b><br />"
88
+ html += "#{user_by.name}<br /><br />"
89
+
90
+ html += "<b>#{_hb.gettext.gettext("Task", user_assigned.locale)}:</b><br />"
91
+ html += "<a href=\"#{url}\">#{url}</a><br /><br />"
92
+
93
+ html += "<b>#{_hb.gettext.gettext("Description", user_assigned.locale)}</b><br />"
94
+ html += self[:descr]
95
+
96
+ _hb.mail(:to => user_assigned[:email], :subject => subj, :html => html)
97
+ end
98
+
99
+ #Returns the emails of the assigned users and the owner as an array.
100
+ def notify_emails
101
+ emails = {}
102
+ emails[self.user[:email]] = self.user if self.user and self.user[:email].to_s.strip.length > 0
103
+
104
+ assigned_users.each do |link|
105
+ user = link.user
106
+ next if !user or user[:email].to_s.strip.length <= 0
107
+
108
+ emails[user[:email]] = user
109
+ end
110
+
111
+ ret = []
112
+ emails.each do |email, user|
113
+ ret << {
114
+ :email => email,
115
+ :user => user
116
+ }
117
+ end
118
+
119
+ return ret
120
+ end
121
+
122
+ def url
123
+ return "#{_site.args[:url]}/?show=task_show&task_id=#{id}"
124
+ end
125
+
126
+ def html
127
+ return "<a href=\"?show=task_show&amp;task_id=#{id}\">#{name.html}</a>"
128
+ end
129
+
130
+ def comments(args = {})
131
+ return _ob.list(:Comment, {"object_lookup" => self}.merge(args))
132
+ end
133
+
134
+ def has_access?(user = _site.user)
135
+ return false if !user
136
+ return true if user.has_rank?("admin")
137
+ return true if self[:user_id].to_s == user.id.to_s
138
+ return false
139
+ end
140
+
141
+ def status_str
142
+ return ob.static(:Task, :status_opts)[self[:status].to_sym]
143
+ end
144
+
145
+ def has_view_access?(user)
146
+ return false if !user
147
+ return true if self.has_access?(user)
148
+
149
+ task_link = ob.get_by(:Task_assigned_user, {
150
+ "user" => user,
151
+ "task" => self
152
+ })
153
+ return true if task_link
154
+
155
+ project_link = ob.get_by(:User_project_link, {
156
+ "user" => user,
157
+ "project" => self.project
158
+ })
159
+ return true if project_link
160
+
161
+ return false
162
+ end
163
+ end
@@ -0,0 +1,62 @@
1
+ class Knjtasks::Task_assigned_user < Knj::Datarow
2
+ has_one [
3
+ {:class => :User, :required => true},
4
+ {:class => :Task, :required => true}
5
+ ]
6
+
7
+ def self.list(d)
8
+ sql = "SELECT #{table}.* FROM #{table}"
9
+
10
+ join_task = true if d.args["orderby"] == "project_task_names"
11
+ join_task = true if d.args["task_status_not"]
12
+
13
+ if join_task
14
+ orderby = "project_task_names"
15
+ d.args.delete("orderby")
16
+
17
+ sql += "
18
+ LEFT JOIN Task ON
19
+ Task.id = #{table}.task_id
20
+
21
+ LEFT JOIN Project ON
22
+ Project.id = Task.project_id
23
+ "
24
+ end
25
+
26
+ sql += " WHERE 1=1"
27
+
28
+ ret = list_helper(d)
29
+ d.args.each do |key, val|
30
+ case key
31
+ when "task_status_not"
32
+ sql += " AND Task.status != '#{d.db.esc(val)}'"
33
+ else
34
+ raise sprintf(_("Invalid key: %s."), key)
35
+ end
36
+ end
37
+
38
+ sql += ret[:sql_where]
39
+
40
+ if orderby == "task_name"
41
+ sql += " ORDER BY Project.name, Task.name"
42
+ else
43
+ sql += ret[:sql_order]
44
+ end
45
+
46
+ sql += ret[:sql_limit]
47
+
48
+ return d.ob.list_bysql(table, sql)
49
+ end
50
+
51
+ def self.add(d)
52
+ link = d.ob.get_by(:Task_assigned_user, {
53
+ "task_id" => d.data[:task_id],
54
+ "user_id" => d.data[:user_id]
55
+ })
56
+ raise _("That user is already assigned to that task.") if link
57
+ end
58
+
59
+ def add_after(d)
60
+ task.send_notify_assigned(user, _site.user)
61
+ end
62
+ end
@@ -0,0 +1,26 @@
1
+ class Knjtasks::Task_check < Knj::Datarow
2
+ has_one [
3
+ {:class => :User, :col => :added_user_id, :method => :user_added, :required => true},
4
+ :Task
5
+ ]
6
+
7
+ def self.list(d)
8
+ sql = "SELECT * FROM #{table} WHERE 1=1"
9
+
10
+ ret = list_helper(d)
11
+ d.args.each do |key, val|
12
+ raise sprintf(_("Invalid key: %s."), key)
13
+ end
14
+
15
+ sql += ret[:sql_where]
16
+ sql += ret[:sql_order]
17
+ sql += ret[:sql_limit]
18
+
19
+ return d.ob.list_bysql(:Task_check, sql)
20
+ end
21
+
22
+ def self.add(d)
23
+ d.data[:added_user_id] = _site.user.id if !d.data[:added_user_id] and _site.user
24
+ d.data[:date_added] = Time.now if !d.data[:date_added]
25
+ end
26
+ end
@@ -0,0 +1,82 @@
1
+ class Knjtasks::Timelog < Knj::Datarow
2
+ has_one [
3
+ :Task,
4
+ :User
5
+ ]
6
+
7
+ def self.list(d)
8
+ sql = "SELECT"
9
+
10
+ join_tasks = true if d.args["project_id"]
11
+
12
+ if d.args[:count]
13
+ count = true
14
+ d.args.delete(:count)
15
+ sql += "
16
+ SUM(TIME_TO_SEC(Timelog.time)) AS sum_time,
17
+ SUM(TIME_TO_SEC(Timelog.time_transport)) AS sum_time_transport,
18
+ SUM(Timelog.transport_length) AS sum_transport_length
19
+ "
20
+ else
21
+ sql += " Timelog.*"
22
+ end
23
+
24
+ sql += " FROM Timelog"
25
+
26
+ if join_tasks
27
+ sql += "
28
+ LEFT JOIN Task ON
29
+ Task.id = Timelog.task_id
30
+ "
31
+ end
32
+
33
+ sql += " WHERE 1=1"
34
+
35
+ ret = list_helper(d)
36
+ d.args.each do |key, val|
37
+ case key
38
+ when "project_id"
39
+ if val.is_a?(Array)
40
+ sql += " AND Task.project_id IN (" + Knj::ArrayExt.join(:arr => val, :sep => ",", :surr => "'", :callback => proc{|data| d.db.esc(data)}) + ")"
41
+ else
42
+ sql += " AND Task.project_id = '#{d.db.esc(val)}'"
43
+ end
44
+ else
45
+ raise sprintf(_("Invalid key: %s."), key)
46
+ end
47
+ end
48
+
49
+ sql += ret[:sql_where]
50
+ sql += ret[:sql_order]
51
+
52
+ if count
53
+ return d.db.query(sql).fetch
54
+ end
55
+
56
+ sql += ret[:sql_limit]
57
+
58
+ return d.ob.list_bysql(:Timelog, sql)
59
+ end
60
+
61
+ def self.add(d)
62
+ d.data[:user_id] = _site.user.id if !d.data[:user_id] and _site.user
63
+ raise _("No comment has been written.") if d.data[:comment].to_s.length <= 0
64
+ task = d.ob.get(:Task, d.data[:task_id])
65
+ end
66
+
67
+ def html(args = {})
68
+ if args[:key] == :id_localized
69
+ name = Knj::Locales.number_out(id, 0)
70
+ elsif args[:key]
71
+ name = self[args[:key]].html
72
+ else
73
+ name = sprintf(_("Timelog %s"), Knj::Locales.number_out(id, 0))
74
+ end
75
+
76
+ return "<a href=\"javascript: timelog_edit('#{id}');\">#{name}</a>"
77
+ end
78
+
79
+ def comment_html
80
+ return Php4r.nl2br(Knj::Web.html(self[:comment]))
81
+ end
82
+ end
@@ -0,0 +1,125 @@
1
+ class Knjtasks::User < Knj::Datarow
2
+ has_many [
3
+ {:class => :Task, :col => :user_id, :depends => true},
4
+ {:class => :Task_assigned_user, :col => :user_id, :method => :assigned_to_tasks, :depends => true},
5
+ {:class => :User_rank_link, :col => :user_id, :method => :ranks, :depends => true},
6
+ {:class => :User_project_link, :col => :user_id, :method => :projects, :depends => true},
7
+ [:User_task_list_link, :user_id, :user_lists]
8
+ ]
9
+
10
+ def delete
11
+ raise _("Cannot delete user because that user has created tasks.") if ob.get_by(:Task, {"user" => self})
12
+ end
13
+
14
+ def name
15
+ if self[:name].to_s.length > 0
16
+ return self[:name]
17
+ elsif self[:username].to_s.length > 0
18
+ return self[:username]
19
+ end
20
+
21
+ raise "Could not figure out a name?"
22
+ end
23
+
24
+ def locale
25
+ return self[:locale] if self[:locale].to_s.length > 0
26
+ return "en_GB"
27
+ end
28
+
29
+ def has_rank?(rank_str)
30
+ rank = self.ob.get_by(:User_rank, {
31
+ "id_str" => rank_str
32
+ })
33
+ return false if !rank
34
+
35
+ rank_link = self.ob.get_by(:User_rank_link, {
36
+ "user" => self,
37
+ "rank" => rank
38
+ })
39
+ return false if !rank_link
40
+
41
+ return true
42
+ end
43
+
44
+ def has_email?
45
+ return Knj::Strings.is_email?(self[:email])
46
+ end
47
+
48
+ def html
49
+ name_str = self[:name]
50
+ if name_str.to_s.strip.length <= 0
51
+ name_str = self[:username]
52
+ end
53
+
54
+ if name_str.to_s.strip.length <= 0
55
+ name_str = "[#{_("no name")}]"
56
+ end
57
+
58
+ return "<a href=\"?show=user_show&amp;user_id=#{id}\">#{name_str.html}</a>"
59
+ end
60
+
61
+ def has_view_access?(user)
62
+ return false if !user
63
+ return true if user.has_rank?("admin")
64
+
65
+ res = _db.query("
66
+ SELECT
67
+ *
68
+
69
+ FROM
70
+ User_project_link AS user_link,
71
+ User_project_link AS my_link
72
+
73
+ WHERE
74
+ user_link.user_id = '#{_db.esc(user.id)}' AND
75
+ my_link.user_id = '#{_db.esc(self.id)}' AND
76
+ user_link.project_id = my_link.project_id
77
+
78
+ LIMIT
79
+ 1
80
+ ").fetch
81
+ return true if res
82
+
83
+ return false
84
+ end
85
+
86
+ def customers
87
+ customers = {}
88
+ self.ob.list(:Project, {
89
+ [:User_project_link, "user_id"] => self.id
90
+ }) do |project|
91
+ customer = project.customer
92
+ next if !customer or customers.key?(customer.id)
93
+ customers[customer.id] = customer
94
+ end
95
+
96
+ customers = customers.values.to_a
97
+ customers.sort do |cust1, cust2|
98
+ cust1.name.downcase <=> cust2.name.downcase
99
+ end
100
+
101
+ return customers
102
+ end
103
+
104
+ #A list of all relevant users for this user (from the same customer).
105
+ def users_list
106
+ users = {}
107
+
108
+ self.customers.each do |customer|
109
+ customer.projects do |project|
110
+ self.ob.list(:User, {
111
+ [:User_project_link, "project_id"] => project.id
112
+ }) do |user|
113
+ users[user.id] = user
114
+ end
115
+ end
116
+ end
117
+
118
+ users = users.values
119
+ users.sort do |user1, user2|
120
+ user1.name.downcase <=> user2.name.downcase
121
+ end
122
+
123
+ return users
124
+ end
125
+ end