knjtasks 0.0.3

Sign up to get free protection for your applications and to get access to all the features.
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