# Task is the base class of ... tasks! Its instances represents tasks that must # be confirmed by someone (like an environment administrator) or by noosfero # itself. # # The specific types of tasks must override the #perform method, so # the actual action associated to the type of task can be performed. See the # documentation of the #perform method for details. # # This class has a +data+ field of type text, where you can store any # type of data (as serialized Ruby objects) you need for your subclass (which # will need to declare itself). class Task < ApplicationRecord extend ActsAsHavingSettings::ClassMethods acts_as_having_settings field: :data module Status # the status of tasks just created ACTIVE = 1 # the status of a task that was cancelled. CANCELLED = 2 # the status of a task that was successfully finished FINISHED = 3 # the status of a task that was created but is not displayed yet HIDDEN = 4 def self.names [nil, N_('Active'), N_('Cancelled'), N_('Finished'), N_('Hidden')] end end include Noosfero::Plugin::HotSpot belongs_to :requestor, :class_name => 'Profile', :foreign_key => :requestor_id belongs_to :target, :foreign_key => :target_id, :polymorphic => true belongs_to :responsible, :class_name => 'Person', :foreign_key => :responsible_id belongs_to :closed_by, :class_name => 'Person', :foreign_key => :closed_by_id validates_uniqueness_of :code, :on => :create validates_presence_of :code attr_protected :status settings_items :email_template_id, :type => :integer def initialize(*args) super self.status = (args.first ? args.first[:status] : nil) || Task::Status::ACTIVE end attr_accessor :code_length before_validation(:on => :create) do |task| if task.code.nil? task.code = Task.generate_code(task.code_length) while Task.from_code(task.code).first task.code = Task.generate_code(task.code_length) end end end after_create do |task| unless task.status == Task::Status::HIDDEN begin task.send(:send_notification, :created) rescue NotImplementedError => ex Rails.logger.info ex.to_s end begin target_msg = task.target_notification_message if target_msg && task.target && !task.target.notification_emails.empty? if target_profile_accepts_notification?(task) TaskMailer.target_notification(task, target_msg).deliver end end rescue NotImplementedError => ex Rails.logger.info ex.to_s end end end def target_profile_accepts_notification?(task) if task.target.kind_of? Organization return task.target.profile_admin_mail_notification else true end end # this method finished the task. It calls #perform, which must be overriden # by subclasses. At the end a message (as returned by #finish_message) is # sent to the requestor with #notify_requestor. def finish(closed_by=nil) transaction do close(Task::Status::FINISHED, closed_by) self.perform begin send_notification(:finished) rescue NotImplementedError => ex Rails.logger.info ex.to_s end end after_finish end # :nodoc: def after_finish end def reject_explanation=(reject_explanation='') self.data[:reject_explanation] = reject_explanation end def reject_explanation self.data[:reject_explanation] end # this method cancels the task. At the end a message (as returned by # #cancel_message) is sent to the requestor with #notify_requestor. def cancel(closed_by=nil) transaction do close(Task::Status::CANCELLED, closed_by) begin send_notification(:cancelled) rescue NotImplementedError => ex Rails.logger.info ex.to_s end end end class KindOfValidator < ActiveModel::EachValidator def validate_each(record, attribute, value) environment = record.environment || Environment.default klass = options[:kind] group = klass.to_s.downcase.pluralize id = attribute.to_s + "_id" if environment.respond_to?(group) attrb = value || environment.send(group).find_by(id: record.send(id)) else attrb = value || klass.find_by(id: record.send(id)) end if attrb.respond_to?(klass.to_s.downcase + "?") unless attrb.send(klass.to_s.downcase + "?") record.errors[attribute] << (options[:message] || "should be "+ klass.to_s.downcase) end else unless attrb.class == klass record.errors[attribute] << (options[:message] || "should be "+ klass.to_s.downcase) end end end end def close(status, closed_by) self.status = status self.end_date = Time.now self.closed_by = closed_by self.save! end # Here are the tasks customizable options. def title _("Task") end def subject nil end def linked_subject nil end def information {:message => _('%{requestor} sent you a task.')} end def accept_details false end def reject_details false end def custom_fields_moderate false end def footer false end def icon {:type => :defined_image, :src => "/images/icons-app/user-minor.png", :name => requestor.name, :url => requestor.url} end def default_decision 'skip' end def accept_disabled? false end def reject_disabled? false end def skip_disabled? false end # The message that will be sent to the requestor of the task when the task is # created. def task_created_message raise NotImplementedError, "#{self} does not implement #task_created_message" end # The message that will be sent to the requestor of the task when its # finished. def task_finished_message raise NotImplementedError, "#{self} does not implement #task_finished_message" end # The message that will be sent to the requestor of the task when its # cancelled. def task_cancelled_message raise NotImplementedError, "#{self} does not implement #task_cancelled_message" end # The message that will be sent to the requestor of the task when its # activated. def task_activated_message raise NotImplementedError, "#{self} does not implement #task_cancelled_message" end # The message that will be sent to the *target* of the task when it is # created. The indent of this message is to notify the target about the # request that was just created for him/her. # # The implementation in this class returns +nil+, what makes the notification # not to be sent. If you want to send a notification to the target upon task # creation, override this method and return a String. def target_notification_message raise NotImplementedError, "#{self} does not implement #target_notification_message" end def target_notification_description '' end # What permission is required to perform task? def permission :perform_task end def environment return target if target.kind_of?(Environment) self.target.environment unless self.target.nil? end def activate self.status = Task::Status::ACTIVE save! begin self.send(:send_notification, :activated) rescue NotImplementedError => ex Rails.logger.info ex.to_s end begin target_msg = target_notification_message if target_msg && self.target && !self.target.notification_emails.empty? TaskMailer.target_notification(self, target_msg).deliver end rescue NotImplementedError => ex Rails.logger.info ex.to_s end end def email_template @email_template ||= email_template_id.present? ? EmailTemplate.find_by_id(email_template_id) : nil end def to_liquid HashWithIndifferentAccess.new({ :requestor => requestor, :reject_explanation => reject_explanation, :code => code }) end scope :pending, -> { where status: Task::Status::ACTIVE } scope :hidden, -> { where status: Task::Status::HIDDEN } scope :finished, -> { where status: Task::Status::FINISHED } scope :canceled, -> { where status: Task::Status::CANCELLED } scope :closed, -> { where status: [Task::Status::CANCELLED, Task::Status::FINISHED] } scope :opened, -> { where status: [Task::Status::ACTIVE, Task::Status::HIDDEN] } scope :of, -> type { where :type => type if type } scope :order_by, -> attribute, ord { if ord.downcase.include? 'desc' order attribute.to_sym => :desc else order attribute.to_sym end } scope :like, -> field, value { if value and Task.column_names.include? field where "LOWER(#{field}) LIKE ?", "%#{value.downcase}%" end } scope :pending_all, -> profile, filter_type, filter_text { self.to(profile).without_spam.pending.of(filter_type).like('data', filter_text) } scope :to, lambda { |profile| environment_condition = nil if profile.person? envs_ids = Environment.all.select{ |env| profile.is_admin?(env) }.map{ |env| "target_id = #{env.id}"}.join(' OR ') environment_condition = envs_ids.blank? ? nil : "(target_type = 'Environment' AND (#{envs_ids}))" end profile_condition = "(target_type = 'Profile' AND target_id = #{profile.id})" where [environment_condition, profile_condition].compact.join(' OR ') } scope :from_closed_date, -> closed_from { where('tasks.end_date >= ?', closed_from.beginning_of_day) unless closed_from.blank? } scope :until_closed_date, -> closed_until { where('tasks.end_date <= ?', closed_until.end_of_day) unless closed_until.blank? } scope :from_creation_date, -> created_from { where('tasks.created_at >= ?', created_from.beginning_of_day) unless created_from.blank? } scope :until_creation_date, -> created_until { where('tasks.created_at <= ?', created_until.end_of_day) unless created_until.blank? } def self.pending_types_for(profile) Task.to(profile).pending.select('distinct type').map { |t| [t.class.name, t.title] } end def self.closed_types_for(profile) Task.to(profile).closed.select('distinct type').map { |t| [t.class.name, t.title] } end def opened? status == Task::Status::ACTIVE || status == Task::Status::HIDDEN end include Spammable #FIXME make this test def display_to?(user = nil) return true if self.target == user return false if !self.target.kind_of?(Environment) && self.target.person? if self.target.kind_of?(Environment) user.is_admin?(self.target) else self.target.members.by_role(self.target.roles.reject {|r| !r.has_permission?('perform_task')}).include?(user) end end protected # This method must be overrided in subclasses, and its implementation must do # the job the task is intended to. This method will be called when the finish # method is called. # # To cancel the finish of the task, you can throw an exception in perform. # # The implementation on Task class just does nothing. def perform end # Tells wheter e-mail notifications must be sent or not. Returns # true by default (i.e. notification are sent), but can be overriden # in subclasses to disable notifications or even to send notifications based # on some conditions. def sends_email? true end # sends notification e-mail about a task, if the task has a requestor. # # If def send_notification(action) if sends_email? if self.requestor && !self.requestor.notification_emails.empty? message = TaskMailer.generic_message("task_#{action}", self) message.deliver if message end end end # finds a task by its (generated) code. Only returns a task with the # specified code AND with status = Task::Status::ACTIVE. # # Can be used in subclasses to find only their instances. scope :from_code, -> code { where code: code, status: Task::Status::ACTIVE } class << self # generates a random code string consisting of length characters (or 36 by # default) in the ranges a-z and 0-9 def generate_code(length = nil) chars = ('a'..'z').to_a + ('0'..'9').to_a code = "" (length || chars.size).times do |n| code << chars[rand(chars.size)] end code end def per_page 15 end end end