diff --git a/app/models/article.rb b/app/models/article.rb index 03ad204..7deae38 100644 --- a/app/models/article.rb +++ b/app/models/article.rb @@ -12,6 +12,7 @@ class Article < ActiveRecord::Base :author, :display_preview, :published_at, :person_followers acts_as_having_image + include Noosfero::Plugin::HotSpot SEARCHABLE_FIELDS = { :name => {:label => _('Name'), :weight => 10}, diff --git a/app/models/task.rb b/app/models/task.rb index 9c89499..6db5183 100644 --- a/app/models/task.rb +++ b/app/models/task.rb @@ -31,6 +31,8 @@ class Task < ActiveRecord::Base 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 @@ -253,6 +255,7 @@ class Task < ActiveRecord::Base end def environment + return target if target.kind_of?(Environment) self.target.environment unless self.target.nil? end diff --git a/plugins/push_notification/Gemfile b/plugins/push_notification/Gemfile new file mode 100644 index 0000000..989fd1a --- /dev/null +++ b/plugins/push_notification/Gemfile @@ -0,0 +1 @@ +gem "gcm" diff --git a/plugins/push_notification/README b/plugins/push_notification/README new file mode 100644 index 0000000..69d3619 --- /dev/null +++ b/plugins/push_notification/README @@ -0,0 +1,140 @@ +README - GCM - Google Cloud Message Plugin +========================================== + +This plugin enables push notifications for mobile platforms +using Google Cloud Message infrastructure. + +INSTALL +======= + +Enable Plugin +------------- + +You need to enable GCM Plugin on your Noosfero: + +cd +./script/noosfero-plugins install gcm +./script/noosfero-plugins enable gcm + +Activation and Plugin Configuration +----------------------------------- + +As a Noosfero administrator user, go to administrator panel: + +- Click on "Enable/disable plugins" option +- Click on "Google Cloud Message Plugin" check-box +- Click on "Configurations" just below the previous step +- *Fill in the form with your server API key registered for GCM +- Select from the check-boxes the notifications you want GCM plugin to send. + +*API key can be obtained creating a project in the GCM console, needed when preparing the application to use push notification. +Link below: + +- https://console.developers.google.com/start + +More information can be found in the following links: +- https://developers.google.com/cloud-messaging/gcm +- https://developers.google.com/cloud-messaging/server +- https://developers.google.com/cloud-messaging/registration +- https://developers.google.com/instance-id/ + +After that, the mobile application need to take care of device registration to GCM and send the device token +to GCM plugin through noosfero API. There are endpoints to add, see and remove device tokens. + +- post request to /api/v1/push_notification_plugin/device_tokens with "tokens" parameter like token1,token2,token3 +will register the tokens token1, token2 and token3 to the api current logged user, defined by noosfero's private token +also passed as parameter + +- get request to /api/v1/push_notification_plugin/device_tokens +will return a list with all the tokens for the current user logged in the api + +- delete request to /api/v1/push_notification_plugin/device_tokens with "tokens" parameter like token1,token2 +will remove token1 and token2 from the list of tokens for the logged user + +- get request to /api/v1/possible_notifications +will return a list with all possible notifications available for user to activate/deactivate + +- get request to /api/v1/notification_settings +will return the notification settings for the logged user + +- post request to /api/v1/notification_settings +will activate/deactivate each notification passed as parameter. For instance, when receiving "new comment" as a parameter with value 1, +the notifications for "new comment" will be activated for the logged user + +- get request to /api/v1/active_notifications +will return a list with all active notifications for the logged user + +- get request to /api/v1/inactive_notifications +will return a list with all inactive notifications for the logged user + +In any endpoint, there is an option to pass target_id so you can add, see and delete tokens of other users. +However, only the admin has permission to do so. Normal users can pass target_id option with it's own id, +but the behavior is the same as passing no parameter. + +As a noosfero user, you can change what notifications you want to receive through web interface as well. In your profile control panel, +go to "Notification" and activate/deactivate notifications from there. + + +Add Users to be notificated from another plugin +------------------------------------------------ + +As a noosfero plugin developer, you can have your plugin to add users to be notified in some notifications implemented here. +The way to do it is following the steps: + + 1 - Create a class with a callback for the specific notification you want to add users to. For instance, add users to the +new comment notification creating a class with a method named** "push_notification_new_comment_additional_users". This method needs to +return an Array with the list of users to be notified. Don't worry, no user will be notified twice. + 2 - Subscribe the class calling the method PushNotificationPlugin::subscribe(environment,notification,klass). For a class CommentCallBack, +call "PushNotificationPlugin::subscribe(, "new_comment", CommentCallBack)", with the environment object instead of +. + +- Unsubscribe at any time calling PushNotificationPlugin::unsubscribe(environment, notification, klass) +- Check the list of subscribers for any notification calling PushNotificationPlugin::subscribers(environment,notification) + +**The method will always be called "push_notification_" + + "additional_users". The class name does not matter, but it's +important to use the full class name, with namespace, so there is no future problem with the callback. The method need to be a class method, +not an instance method. + +Some details about the subscription methods: + - subscribe: + -> return null if the klass passed do not implement the method for the notification passed + -> return null if the notification is not implemented in PushNotificationPlugin + -> return true/false otherwise with the return of the save method + - unsubscribe: + -> return null if the notification is not implemented in PushNotificationPlugin + -> return true if the klass was sucessfully subscribed + -> return false if the klass was not subscribed or the unsubscription fails by any means + - subscribers: + -> return null if the notification is not implemented in PushNotificationPlugin + -> return empty array if there is no subscribers for that notification in that environment + -> return an array with all the subscribers otherwise + +Translate Plugin +------------------ + +To translate the strings used in the plugin, create a copy of the file "en-US.yml" with your locale and +translate the strings in there for each key. These files are stored in the folder "locales". + +Get Involved +============ + +If you find any bug and/or want to collaborate, please send an e-mail to marcos.rpj2@gmail.com + +LICENSE +======= + +Copyright (c) The Author developers. + +See Noosfero license. + + +AUTHORS +======= + +Marcos Ronaldo Pereira Junior (marcos.rpj2 at gmail.com) +Marcos da Silva Ramos (msramos at outlook.com) + +ACKNOWLEDGMENTS +=============== + +The authors have been supported by SNJ (Secretaria Nacional da Juventude) diff --git a/plugins/push_notification/after_enable.rb b/plugins/push_notification/after_enable.rb new file mode 100644 index 0000000..31bf438 --- /dev/null +++ b/plugins/push_notification/after_enable.rb @@ -0,0 +1,3 @@ +puts "\n" +puts "I: \e[33mMake sure you configure your Google Cloud Message's API key in the administration page!\e[0m" +puts "\n" diff --git a/plugins/push_notification/controllers/push_notification_plugin_admin_controller.rb b/plugins/push_notification/controllers/push_notification_plugin_admin_controller.rb new file mode 100644 index 0000000..fd2bb4c --- /dev/null +++ b/plugins/push_notification/controllers/push_notification_plugin_admin_controller.rb @@ -0,0 +1,19 @@ +require_relative "../lib/notification_settings" + +class PushNotificationPluginAdminController < PluginAdminController + append_view_path File.join(File.dirname(__FILE__) + '/../views') + + def index + @server_settings = Noosfero::Plugin::Settings.new(environment, PushNotificationPlugin) + @settings = @server_settings.notifications || PushNotificationPlugin::NotificationSettings.default_hash_flags + end + + def update + data = params[:server_settings] + data[:notifications] = params[:settings] + @server_settings = Noosfero::Plugin::Settings.new(environment, PushNotificationPlugin, data) + @server_settings.save! + redirect_to :action => "index" + end + +end diff --git a/plugins/push_notification/controllers/push_notification_plugin_myprofile_controller.rb b/plugins/push_notification/controllers/push_notification_plugin_myprofile_controller.rb new file mode 100644 index 0000000..243e0e6 --- /dev/null +++ b/plugins/push_notification/controllers/push_notification_plugin_myprofile_controller.rb @@ -0,0 +1,36 @@ +require_relative "../lib/notification_settings" +require_relative "../lib/device_token" + +class PushNotificationPluginMyprofileController < MyProfileController + + append_view_path File.join(File.dirname(__FILE__) + '/../views') + + def index + @devices = current_user.device_tokens + @settings = filter_notifications current_user.notification_settings.hash_flags + end + + def delete_device + device = PushNotificationPlugin::DeviceToken.find(params["device"]) + device.delete + redirect_to :action => "index" + end + + def update_settings + current_user.notification_settings.set_notifications(params["settings"] || {}) + current_user.save + redirect_to :action => "index" + end + + private + + def filter_notifications hash_flags + server_settings = Noosfero::Plugin::Settings.new(environment, PushNotificationPlugin) + server_notifications = server_settings.notifications || {} + filtered_settings = {} + hash_flags.each do |notification, enabled| + filtered_settings[notification] = enabled if server_notifications[notification] == "1" + end + filtered_settings + end +end diff --git a/plugins/push_notification/db/migrate/20160109195305_create_device_tokens.rb b/plugins/push_notification/db/migrate/20160109195305_create_device_tokens.rb new file mode 100644 index 0000000..da52c4b --- /dev/null +++ b/plugins/push_notification/db/migrate/20160109195305_create_device_tokens.rb @@ -0,0 +1,11 @@ +class CreateDeviceTokens < ActiveRecord::Migration + def change + create_table :push_notification_plugin_device_tokens do |t| + t.integer :user_id, null: false + t.string :device_name, null:false + t.string :token, null: false, unique: true + t.timestamps null: false + t.index :user_id + end + end +end diff --git a/plugins/push_notification/db/migrate/20160110125119_create_notification_settings.rb b/plugins/push_notification/db/migrate/20160110125119_create_notification_settings.rb new file mode 100644 index 0000000..21e839d --- /dev/null +++ b/plugins/push_notification/db/migrate/20160110125119_create_notification_settings.rb @@ -0,0 +1,10 @@ +class CreateNotificationSettings < ActiveRecord::Migration + def change + create_table :push_notification_plugin_notification_settings do |t| + t.integer :user_id, null: false + t.integer :notifications, null: false, :default => 0 + t.timestamps null: false + t.index :user_id + end + end +end diff --git a/plugins/push_notification/db/migrate/20160118192037_create_notification_subscriptions.rb b/plugins/push_notification/db/migrate/20160118192037_create_notification_subscriptions.rb new file mode 100644 index 0000000..52ba370 --- /dev/null +++ b/plugins/push_notification/db/migrate/20160118192037_create_notification_subscriptions.rb @@ -0,0 +1,9 @@ +class CreateNotificationSubscriptions < ActiveRecord::Migration + def change + create_table :push_notification_plugin_notification_subscriptions do |t| + t.string :notification, :unique => true + t.integer :environment_id, null: false + t.text :subscribers + end + end +end diff --git a/plugins/push_notification/dependencies.rb b/plugins/push_notification/dependencies.rb new file mode 100644 index 0000000..7138a2f --- /dev/null +++ b/plugins/push_notification/dependencies.rb @@ -0,0 +1 @@ +require 'gcm' diff --git a/plugins/push_notification/features/push_notification_admin.feature b/plugins/push_notification/features/push_notification_admin.feature new file mode 100644 index 0000000..70f344d --- /dev/null +++ b/plugins/push_notification/features/push_notification_admin.feature @@ -0,0 +1,46 @@ +Feature: push notification administration + As an administrator + I want to configure the push notification plugin + + Background: + Given plugin PushNotification is enabled on environment + Given I am logged in as admin + + Scenario: configure the api key + Given I go to /admin/plugin/push_notification + And I fill in "Server API key" with "ABCDEFGH1234567890" + When I press "Save" + Then I should be on /admin/plugin/push_notification + Then the "Server API key" field should contain "ABCDEFGH1234567890" + + Scenario: change the api key + Given that "old_key" is the server api key + Given I go to /admin/plugin/push_notification + Then the "Server API key" field should contain "old_key" + When I fill in "Server API key" with "new_key" + And I press "Save" + Then I should be on /admin/plugin/push_notification + Then the "Server API key" field should contain "new_key" + + Scenario: enable notifications + Given I go to /admin/plugin/push_notification + And I check "settings_add_member" + And I check "settings_new_article" + When I press "Save" + Then the "settings_add_member" checkbox should be checked + Then the "settings_new_article" checkbox should be checked + + Scenario: disable notifications + Given the following notifications + |name| + |add_friend| + |add_member| + And I go to /admin/plugin/push_notification + Then the "settings_add_friend" checkbox should be checked + Then the "settings_add_member" checkbox should be checked + And I uncheck "settings_add_friend" + And I uncheck "settings_add_member" + When I press "Save" + Then I should be on /admin/plugin/push_notification + Then the "settings_add_friend" checkbox should not be checked + Then the "settings_add_member" checkbox should not be checked diff --git a/plugins/push_notification/features/push_notification_user_config.feature b/plugins/push_notification/features/push_notification_user_config.feature new file mode 100644 index 0000000..c384396 --- /dev/null +++ b/plugins/push_notification/features/push_notification_user_config.feature @@ -0,0 +1,73 @@ +Feature: push notification user config + As an user + I want to configure the notification I want to receive + + Background: + Given plugin PushNotification is enabled on environment + Given the following users + |login | name | + |joaosilva| Joao Silva | + + Scenario: the user should see his devices + Given that the user joaosilva has the following devices + | token | name | + | "token_1" | "Banana Phone" | + | "token_2" | "Zamzung Phone" | + Given I am logged in as "joaosilva" + Given I go to joaosilva's control panel + When I follow "Notifications" + Then I should see "Banana Phone" + And I should see "Zamzung Phone" + + Scenario: the user should delete his devices + Given that the user joaosilva has the following devices + | token | name | + | "token_1" | "Zamzung Phone" | + Given I am logged in as "joaosilva" + Given I go to joaosilva's control panel + When I follow "Notifications" + When I follow "delete_device_1" + Then I should be on /myprofile/joaosilva/plugin/push_notification + And I should not see "Zamzung Phone" + + Scenario: the user should only see the notifications enabled in the environment + Given the following notifications + |name | + |add_friend | + |new_article | + Given I am logged in as "joaosilva" + Given I go to joaosilva's control panel + When I follow "Notifications" + Then I should see "Add Friend" + And I should see "New Article" + And I should not see "New Member" + And I should not see "Approve Article" + + Scenario: the user should be able to enable notifications + Given the following notifications + |name | + |add_friend | + |new_article | + Given I am logged in as "joaosilva" + Given I go to joaosilva's control panel + When I follow "Notifications" + And I check "settings_add_friend" + And I check "settings_new_article" + When I press "Save" + Then the "settings_add_friend" checkbox should be checked + Then the "settings_new_article" checkbox should be checked + + Scenario: the user should be able to disable notifications + Given the following notifications + | name | + | add_friend | + | new_article | + Given that the user joaosilva has the following notifications + | name | + | add_friend | + Given I am logged in as "joaosilva" + Given I go to joaosilva's control panel + When I follow "Notifications" + And I uncheck "settings_add_friend" + When I press "Save" + Then the "settings_add_friend" checkbox should not be checked diff --git a/plugins/push_notification/features/step_definitions/push_notification_steps.rb b/plugins/push_notification/features/step_definitions/push_notification_steps.rb new file mode 100644 index 0000000..2a29351 --- /dev/null +++ b/plugins/push_notification/features/step_definitions/push_notification_steps.rb @@ -0,0 +1,30 @@ +Given /^the following notifications$/ do |table| + settings = {} + table.hashes.each do |item| + settings[item[:name]] = "1" + end + data = {:notifications => settings} + server_settings = Noosfero::Plugin::Settings.new(Environment.default, PushNotificationPlugin, data) + server_settings.save! +end + +Given /^that the user ([^\"]*) has the following devices$/ do |user_name,table| + user = User.find_by(:login => user_name) + table.hashes.each do |item| + PushNotificationPlugin::DeviceToken.create(:user => user, :token => item[:token], :device_name => item[:name]) + end +end + +Given /^that the user ([^\"]*) has the following notifications$/ do |user_name,table| + user = User.find_by(:login => user_name) + table.hashes.each do |item| + user.notification_settings.activate_notification item[:name] + end + user.save! +end + +Given /^that "([^\"]*)" is the server api key$/ do |key| + data = {:server_api_key => key} + server_settings = Noosfero::Plugin::Settings.new(Environment.default, PushNotificationPlugin, data) + server_settings.save! +end diff --git a/plugins/push_notification/lib/device_token.rb b/plugins/push_notification/lib/device_token.rb new file mode 100644 index 0000000..bb96fe7 --- /dev/null +++ b/plugins/push_notification/lib/device_token.rb @@ -0,0 +1,16 @@ +class PushNotificationPlugin::DeviceToken < ActiveRecord::Base + belongs_to :user + attr_accessible :token, :device_name, :user + + after_save :check_notification_settings + + private + + def check_notification_settings + if user.notification_settings.nil? + user.notification_settings=PushNotificationPlugin::NotificationSettings.new + user.save + end + end + +end diff --git a/plugins/push_notification/lib/ext/user.rb b/plugins/push_notification/lib/ext/user.rb new file mode 100644 index 0000000..c4467d8 --- /dev/null +++ b/plugins/push_notification/lib/ext/user.rb @@ -0,0 +1,35 @@ +require_dependency "user" +require_relative "../notification_subscription" +require_relative "../notification_settings" +require_relative "../device_token" + +class User + has_many :device_tokens, class_name: "PushNotificationPlugin::DeviceToken", :dependent => :destroy + has_one :notification_settings, class_name: "PushNotificationPlugin::NotificationSettings", :dependent => :destroy, :autosave => true + + after_save :save_notification_settings + after_initialize :setup_notification_settings + + def device_token_list + device_tokens.map { |t| t.token } + end + + def enabled_notifications + notification_settings.notifications + end + + def enabled_notifications=(notifications) + notification_settings.notifications=notifications + end + + private + + def save_notification_settings + notification_settings.save if notification_settings + end + + def setup_notification_settings + self.notification_settings = PushNotificationPlugin::NotificationSettings.new(:user => self) if self.notification_settings.nil? + end + +end diff --git a/plugins/push_notification/lib/notification_settings.rb b/plugins/push_notification/lib/notification_settings.rb new file mode 100644 index 0000000..84e6518 --- /dev/null +++ b/plugins/push_notification/lib/notification_settings.rb @@ -0,0 +1,69 @@ +class PushNotificationPlugin::NotificationSettings < ActiveRecord::Base + + NOTIFICATIONS= { + "add_friend" => 0x1, + "new_comment" => 0x2, + "add_member" => 0x4, + "suggest_article" => 0x8, + "new_article" => 0x10, + "approve_article" => 0x20, + "add_friend_result" => 0x40, + "add_member_result" => 0x80, + "approve_article_result" => 0x100, + "suggest_article_result" => 0x200 + } + + belongs_to :user + attr_accessible :user, :notifications + + def self.default_hash_flags + default_hash_flags = {} + NOTIFICATIONS.keys.each do |event| + default_hash_flags[event] = "0" + end + default_hash_flags + end + + def hash_flags + flags = {} + NOTIFICATIONS.keys.each do |notification| + flags[notification] = active? notification + end + flags + end + + def active_notifications + NOTIFICATIONS.keys.select{|notification| active?(notification)} + end + + def inactive_notifications + NOTIFICATIONS.keys.select{|notification| !active?(notification)} + end + + def active? notification + ((self.notifications & NOTIFICATIONS[notification])!=0) + end + + def activate_notification notification + self.notifications |= NOTIFICATIONS[notification] + end + + def set_notifications notifications + NOTIFICATIONS.keys.each do |event| + set_notification_state event, notifications[event] + end + end + + def deactivate_notification notification + self.notifications &= ~NOTIFICATIONS[notification] + end + + def set_notification_state notification, state + if state.blank? || (state == 0) || (state == "0") || state == false + deactivate_notification notification + else + activate_notification notification + end + end + +end diff --git a/plugins/push_notification/lib/notification_subscription.rb b/plugins/push_notification/lib/notification_subscription.rb new file mode 100644 index 0000000..e2a18d9 --- /dev/null +++ b/plugins/push_notification/lib/notification_subscription.rb @@ -0,0 +1,9 @@ +class PushNotificationPlugin::NotificationSubscription < ActiveRecord::Base + belongs_to :environment + attr_accessible :subscribers, :notification, :environment + + validates :notification, :uniqueness => true + serialize :subscribers + +end + diff --git a/plugins/push_notification/lib/push_notification_helper.rb b/plugins/push_notification/lib/push_notification_helper.rb new file mode 100644 index 0000000..cb9f2bf --- /dev/null +++ b/plugins/push_notification/lib/push_notification_helper.rb @@ -0,0 +1,46 @@ +module PushNotificationHelper + + def gcm_instance + api_key = settings[:server_api_key] + gcm = GCM.new(api_key) + gcm + end + + def settings + return Noosfero::Plugin::Settings.new(environment, PushNotificationPlugin.class) + end + + #data should be a hash, like {some_info: 123123} + def send_to_users(flag, users, data) + return false unless users.present? + users |= subscribers_additional_users(flag, users.first.environment) + users = filter_users_for_flag(flag, users) + return false unless users.present? + tokens = tokens_for_users(users) + gcm = gcm_instance + response = gcm.send(tokens, data) + response[:response] + end + + def filter_users_for_flag(flag, users) + users.select{|u| u.notification_settings.active?(flag)} + end + + def tokens_for_users(users) + tokens=[] + users.each{|c| tokens+= c.device_token_list} + return tokens + end + + def subscribers_additional_users notification, environment + subs = PushNotificationPlugin::subscribers(environment, notification) + users=[] + if subs.present? + subs.each do |s| + users+=s.send("push_notification_#{notification}_additional_users".to_sym) + end + end + users + end + +end diff --git a/plugins/push_notification/lib/push_notification_plugin.rb b/plugins/push_notification/lib/push_notification_plugin.rb new file mode 100644 index 0000000..b14d089 --- /dev/null +++ b/plugins/push_notification/lib/push_notification_plugin.rb @@ -0,0 +1,62 @@ +class PushNotificationPlugin < Noosfero::Plugin + + include Noosfero::Plugin::HotSpot + include PushNotificationPlugin::Observers + + def self.plugin_name + I18n.t("push_notification_plugin.lib.plugin.name") + end + + def self.subscribe environment, notification, klass + return nil unless PushNotificationPlugin::NotificationSettings::NOTIFICATIONS.keys.include?(notification) + return nil unless klass.name.constantize.respond_to?("push_notification_#{notification}_additional_users".to_sym) + + notification_subscription = PushNotificationPlugin::NotificationSubscription.where(:notification => notification).first + notification_subscription ||= PushNotificationPlugin::NotificationSubscription.new({:notification => notification, + :environment => environment, :subscribers => [klass.name]}) + + notification_subscription.subscribers |= [klass.name] + notification_subscription.save + end + + def self.unsubscribe environment, notification, klass + return nil unless PushNotificationPlugin::NotificationSettings::NOTIFICATIONS.keys.include?(notification) + notification_subscription = PushNotificationPlugin::NotificationSubscription.where(:notification => notification, :environment => environment).first + unless notification_subscription.blank? + if notification_subscription.subscribers.include?(klass.name) + notification_subscription.subscribers -= [klass.name] + return notification_subscription.save + end + end + return false + end + + def self.subscribers environment, notification + return nil unless PushNotificationPlugin::NotificationSettings::NOTIFICATIONS.keys.include?(notification) + notification_subscription = PushNotificationPlugin::NotificationSubscription.where(:notification => notification, :environment => environment) + return [] if notification_subscription.blank? + notification_subscription.first.subscribers.map{|s| s.constantize} + end + + def self.api_mount_points + [PushNotificationPlugin::API] + end + + def self.plugin_description + I18n.t("push_notification_plugin.lib.plugin.description") + end + + def stylesheet? + true + end + + def control_panel_buttons + if context.profile.person? + { + title: I18n.t("push_notification_plugin.lib.plugin.panel_button"), + icon: 'push-notifications', + url: {controller: 'push_notification_plugin_myprofile', action: 'index'} + } + end + end +end diff --git a/plugins/push_notification/lib/push_notification_plugin/api.rb b/plugins/push_notification/lib/push_notification_plugin/api.rb new file mode 100644 index 0000000..6b7cf91 --- /dev/null +++ b/plugins/push_notification/lib/push_notification_plugin/api.rb @@ -0,0 +1,96 @@ +require File.dirname(__FILE__) + '/../../../../../lib/noosfero/api/helpers' +require_relative 'api_entities' + +class PushNotificationPlugin::API < Grape::API + + include Noosfero::API::APIHelpers + + resource :push_notification_plugin do + + helpers do + def target + if params.has_key?(:target_id) + target_user = environment.users.detect{|u|u.id == params[:target_id].to_i} + else + target_user = current_user + end + + if !current_person.is_admin? && (target_user.nil? || target_user!=current_user) + render_api_error!(_('Unauthorized'), 401) + end + + return target_user + end + end + + get 'device_tokens' do + authenticate! + target_user = target + tokens = target_user.device_token_list || [] + present tokens + end + + post 'device_tokens' do + authenticate! + target_user = target + + bad_request!("device_name") unless params[:device_name] + token = PushNotificationPlugin::DeviceToken.new({:device_name => params[:device_name], :token => params[:token], :user => target_user}) if !target_user.device_token_list.include?(params[:token]) + + target_user.device_tokens.push(token) + + + unless target_user.save + render_api_errors!(target_user.errors.full_messages) + end + present target_user, :with => PushNotificationPlugin::Entities::DeviceUser + end + + delete 'device_tokens' do + authenticate! + target_user = target + + PushNotificationPlugin::DeviceToken.delete_all(["token = ? AND user_id = (?)", params[:token],target_user.id]) + + present target_user, :with => PushNotificationPlugin::Entities::DeviceUser + end + + get 'notification_settings' do + authenticate! + target_user = target + + present target_user, with: PushNotificationPlugin::Entities::DeviceUser + end + + get 'possible_notifications' do + result = {:possible_notifications => PushNotificationPlugin::NotificationSettings::NOTIFICATIONS.keys} + present result, with: Grape::Presenters::Presenter + end + + post 'notification_settings' do + authenticate! + target_user = target + + PushNotificationPlugin::NotificationSettings::NOTIFICATIONS.keys.each do |notification| + next unless params.keys.include?(notification) + state = params[notification] + target_user.notification_settings.set_notification_state notification, state + end + + target_user.save! + present target_user, with: PushNotificationPlugin::Entities::DeviceUser + end + + get 'active_notifications' do + authenticate! + target_user = target + present target_user.notification_settings.active_notifications, with: Grape::Presenters::Presenter + end + + get 'inactive_notifications' do + authenticate! + target_user = target + present target_user.notification_settings.inactive_notifications, with: Grape::Presenters::Presenter + end + end +end diff --git a/plugins/push_notification/lib/push_notification_plugin/api_entities.rb b/plugins/push_notification/lib/push_notification_plugin/api_entities.rb new file mode 100644 index 0000000..7d93d5d --- /dev/null +++ b/plugins/push_notification/lib/push_notification_plugin/api_entities.rb @@ -0,0 +1,9 @@ +module PushNotificationPlugin::Entities + class DeviceUser < Noosfero::API::Entities::User + expose :device_token_list, :as => :device_tokens + expose :notification_settings do |user, options| + user.notification_settings.hash_flags + end + end + +end diff --git a/plugins/push_notification/lib/push_notification_plugin/observers.rb b/plugins/push_notification/lib/push_notification_plugin/observers.rb new file mode 100644 index 0000000..249400f --- /dev/null +++ b/plugins/push_notification/lib/push_notification_plugin/observers.rb @@ -0,0 +1,6 @@ +require_relative '../device_token' +Dir[File.dirname(__FILE__) + "/observers/*"].each {|file| require file} +module PushNotificationPlugin::Observers + include PushNotificationHelper + constants.collect{|const_name| const_get(const_name)}.select {|const| const.class == Module}.each{|submodule| include submodule} +end diff --git a/plugins/push_notification/lib/push_notification_plugin/observers/add_friend_observer.rb b/plugins/push_notification/lib/push_notification_plugin/observers/add_friend_observer.rb new file mode 100644 index 0000000..38cfca2 --- /dev/null +++ b/plugins/push_notification/lib/push_notification_plugin/observers/add_friend_observer.rb @@ -0,0 +1,35 @@ +module PushNotificationPlugin::Observers + module AddFriendObserver + def add_friend_after_create_callback(add_friend) + requestor = add_friend.requestor + target = add_friend.target + + send_to_users("add_friend", + [target.user], + {:event => "Add Friend", + :requestor_id => requestor.id, + :requestor_name => requestor.name, + :task_id => add_friend.id} + ) + end + + #check when task is finished + def add_friend_after_save_callback(add_friend) + requestor = add_friend.requestor + target = add_friend.target + + return false unless [Task::Status::FINISHED, Task::Status::CANCELLED].include?(add_friend.status) + + added = add_friend.status==Task::Status::FINISHED + event= added ? "Friendship accepted" : "Friendship refused" + + send_to_users("add_friend_result", + [requestor.user], + {:event => event, + :target_id => target.id, + :target_name => target.name, + :task_id => add_friend.id} + ) + end + end +end diff --git a/plugins/push_notification/lib/push_notification_plugin/observers/add_member_observer.rb b/plugins/push_notification/lib/push_notification_plugin/observers/add_member_observer.rb new file mode 100644 index 0000000..e503fd7 --- /dev/null +++ b/plugins/push_notification/lib/push_notification_plugin/observers/add_member_observer.rb @@ -0,0 +1,36 @@ +module PushNotificationPlugin::Observers + module AddMemberObserver + def add_member_after_create_callback(add_member) + requestor = add_member.requestor + target = add_member.target + + users = target.admins.map{|person| person.user} + + send_to_users("add_member", + users, + {:event => "Add Member to Organization", + :requestor_id => requestor.id, + :requestor_name => requestor.name, + :task_id => add_member.id} + ) + end + + def add_member_after_save_callback(add_member) + requestor = add_member.requestor + target = add_member.target + + return false unless [Task::Status::FINISHED, Task::Status::CANCELLED].include?(add_member.status) + + accepted = add_member.status==Task::Status::FINISHED + event= accepted ? "Membership accepted" : "Membership rejected" + + send_to_users("add_member_result", + [requestor], + {:event => event, + :target_id => target.id, + :target_name => target.name, + :task_id => add_member.id} + ) + end + end +end diff --git a/plugins/push_notification/lib/push_notification_plugin/observers/approve_article_observer.rb b/plugins/push_notification/lib/push_notification_plugin/observers/approve_article_observer.rb new file mode 100644 index 0000000..f6e8d31 --- /dev/null +++ b/plugins/push_notification/lib/push_notification_plugin/observers/approve_article_observer.rb @@ -0,0 +1,42 @@ +module PushNotificationPlugin::Observers + module ApproveArticleObserver + def approve_article_after_create_callback(approve_article) + requestor = approve_article.requestor + target = approve_article.target + + if target.person? + users = [target.user] + elsif target.organization? + users = target.admins.map{|person| person.user} + end + + send_to_users("approve_article", + users, + {:event => "Approve Article", + :requestor_id => requestor.id, + :requestor_name => requestor.name, + :article => approve_article.article, + :task_id => approve_article.id} + ) + end + + def approve_article_after_save_callback(approve_article) + requestor = approve_article.requestor + target = approve_article.target + + return false unless [Task::Status::FINISHED, Task::Status::CANCELLED].include?(approve_article.status) + + accepted = approve_article.status==Task::Status::FINISHED + event= accepted ? "Article approved" : "Article rejected" + + send_to_users("approve_article_result", + [requestor], + {:event => event, + :target_id => target.id, + :target_name => target.name, + :article => approve_article.article, + :task_id => approve_article.id} + ) + end + end +end diff --git a/plugins/push_notification/lib/push_notification_plugin/observers/article_observer.rb b/plugins/push_notification/lib/push_notification_plugin/observers/article_observer.rb new file mode 100644 index 0000000..626be2c --- /dev/null +++ b/plugins/push_notification/lib/push_notification_plugin/observers/article_observer.rb @@ -0,0 +1,24 @@ +module PushNotificationPlugin::Observers + module ArticleObserver + def article_after_create_callback(article) + users=[] + + if article.profile.organization? + article.profile.members.each do |person| + users |= [person.user] if person.user.present? + end + elsif article.profile.person? + users |= [article.profile.user] if article.profile.user.present? + end + + send_to_users("new_article", + users, + {:event => "New article", + :article => article.id, + :article_body => article.body, + :article_title => article.title, + :article_name => article.name, + :author => article.author_name}) + end + end +end diff --git a/plugins/push_notification/lib/push_notification_plugin/observers/comment_observer.rb b/plugins/push_notification/lib/push_notification_plugin/observers/comment_observer.rb new file mode 100644 index 0000000..d89814e --- /dev/null +++ b/plugins/push_notification/lib/push_notification_plugin/observers/comment_observer.rb @@ -0,0 +1,16 @@ +module PushNotificationPlugin::Observers + module CommentObserver + def comment_after_create_callback(comment) + users = comment.source.person_followers.map{|p| p.user} + + send_to_users("new_comment", + users, + {:event => "New Comment", + :article => comment.source.id, + :comment_body => comment.body, + :comment_title => comment.title, + :comment_name => comment.name, + :author => comment.author.name}) + end + end +end diff --git a/plugins/push_notification/lib/push_notification_plugin/observers/suggest_article_observer.rb b/plugins/push_notification/lib/push_notification_plugin/observers/suggest_article_observer.rb new file mode 100644 index 0000000..b4b944f --- /dev/null +++ b/plugins/push_notification/lib/push_notification_plugin/observers/suggest_article_observer.rb @@ -0,0 +1,42 @@ +module PushNotificationPlugin::Observers + module SuggestArticleObserver + def suggest_article_after_create_callback(suggest_article) + requestor = suggest_article.requestor + target = suggest_article.target + + if target.person? + users = [target.user] + elsif target.organization? + users = target.admins.map{|person| person.user} + end + + send_to_users("suggest_article", + users, + {:event => "Add Member", + :requestor_id => requestor.id, + :requestor_name => requestor.name, + :article => suggest_article.article, + :task_id => suggest_article.id} + ) + end + + def suggest_article_after_save_callback(suggest_article) + requestor = suggest_article.requestor + target = suggest_article.target + + return false unless [Task::Status::FINISHED, Task::Status::CANCELLED].include?(suggest_article.status) + + accepted = suggest_article.status==Task::Status::FINISHED + event= accepted ? "Article approved" : "Article rejected" + + send_to_users("suggest_article_result", + [requestor], + {:event => event, + :target_id => target.id, + :target_name => target.name, + :article => suggest_article.article, + :task_id => suggest_article.id} + ) + end + end +end diff --git a/plugins/push_notification/locales/en-US.yml b/plugins/push_notification/locales/en-US.yml new file mode 100644 index 0000000..69bdde4 --- /dev/null +++ b/plugins/push_notification/locales/en-US.yml @@ -0,0 +1,41 @@ +en-US: &en-US + + push_notification_plugin: + events: + add_friend: "Add Friend" + new_comment: "New Comment" + add_member: "New Member" + suggest_article: "Suggest Article" + new_article: "New Article" + approve_article: "Approve Article" + add_friend_result: "Friendship Request Result" + add_member_result: "Membership Request Result" + approve_article_result: "Article approval Result" + suggest_article_result: "Article suggestion Result" + + lib: + plugin: + name: "Push Notifications" + description: "Send push notifications to mobile devices using Google Cloud Message" + panel_button: "Notifications" + settings: "Push Notification Settings" + notifications: "Notifications" + enabled_notifications: "Enabled Notifications" + device: "Device" + actions: "Actions" + + controllers: + myprofile: + my_devices: "My Devices" + no_devices: "No device connected to your account" + delete_device: "Delete Device" + event: "Event" + enabled: "Enabled?" + + admin: + server_api_key: "Server API key" + +en_US: + <<: *en-US +en: + <<: *en-US diff --git a/plugins/push_notification/locales/pt-BR.yml b/plugins/push_notification/locales/pt-BR.yml new file mode 100644 index 0000000..68ba2b0 --- /dev/null +++ b/plugins/push_notification/locales/pt-BR.yml @@ -0,0 +1,41 @@ +pt-BR: &pt-BR + + push_notification_plugin: + events: + add_friend: "Nova Amizade" + new_comment: "Novo Comentário" + add_member: "Novo Membro" + suggest_article: "Artigo Sugerido" + new_article: "Novo Artigo" + approve_article: "Aprovar Artigo" + add_friend_result: "Resultado de Solicitação de Amizade" + add_member_result: "Resultado de Solicitação de participação de comunidade" + approve_article_result: "Resultado de Aprovação de Artigo" + suggest_article_result: "Resultado de Sugestão de Artigo" + + lib: + plugin: + name: "Notificações Push" + description: "Envie notificações para dispositivos móveis usando o Google Cloud Message" + panel_button: "Notificações" + settings: "Configuração das Notificações Push" + notifications: "Notificações" + enabled_notifications: "Notificações Habilitadas" + device: "Dispositivo" + actions: "Ações" + + controllers: + myprofile: + my_devices: "Meus Dispositivos" + no_devices: "Nenhum dispositivo conectado à sua conta." + delete_device: "Apagar Dispositivo" + event: "Evento" + enabled: "Ativo?" + + admin: + server_api_key: "Chave da API do Servidor" + +pt_BR: + <<: *pt-BR +pt: + <<: *pt-BR diff --git a/plugins/push_notification/public/images/control-panel/push-notification-settings.jpg b/plugins/push_notification/public/images/control-panel/push-notification-settings.jpg new file mode 100644 index 0000000..2b94ebc Binary files /dev/null and b/plugins/push_notification/public/images/control-panel/push-notification-settings.jpg differ diff --git a/plugins/push_notification/public/images/control-panel/push-notification-settings.png b/plugins/push_notification/public/images/control-panel/push-notification-settings.png new file mode 100644 index 0000000..472a355 Binary files /dev/null and b/plugins/push_notification/public/images/control-panel/push-notification-settings.png differ diff --git a/plugins/push_notification/public/style.css b/plugins/push_notification/public/style.css new file mode 100644 index 0000000..5ab539a --- /dev/null +++ b/plugins/push_notification/public/style.css @@ -0,0 +1,8 @@ +.controller-profile_editor a.control-panel-push-notifications { + background-image: url("/plugins/push_notification/images/control-panel/push-notification-settings.png"); +} + +.controller-profile_editor .msie6 a.control-panel-push-notifications { + background-image: url("/plugins/push_notification/images/control-panel/push-notification-settings.jpg"); +} + diff --git a/plugins/push_notification/test/helpers/observers_test_helper.rb b/plugins/push_notification/test/helpers/observers_test_helper.rb new file mode 100644 index 0000000..b1749ff --- /dev/null +++ b/plugins/push_notification/test/helpers/observers_test_helper.rb @@ -0,0 +1,33 @@ +module ObserversTestHelper + + def create_add_friend_task + user1 = fast_create(User) + person = fast_create(Person, :user_id => user1.id) + user2 = fast_create(User) + friend = fast_create(Person, :user_id => user2.id) + return AddFriend.create!(:requestor => person, :target => friend) + end + + def create_add_member_task + person = fast_create(Person) + community = fast_create(Community) + return AddMember.create!(:requestor => person, :target => community) + end + + def create_suggest_article_task + person = fast_create(Person) + community = fast_create(Community) + return SuggestArticle.create!(:target => community, :article => {:name => 'Munchkin', :body => 'Kill monsters!! Get treasures!! Stab your friends!!'}, :requestor => person) + end + + def create_approve_article_task + user1 = fast_create(User) + person = fast_create(Person, :user_id => user1.id) + article = fast_create(Article, :profile_id => person.id) + community = fast_create(Community) + community.add_member(person) + community.save! + + return ApproveArticle.create!(:article => article, :target => community, :requestor => person) + end +end diff --git a/plugins/push_notification/test/unit/api_test.rb b/plugins/push_notification/test/unit/api_test.rb new file mode 100644 index 0000000..5ac4357 --- /dev/null +++ b/plugins/push_notification/test/unit/api_test.rb @@ -0,0 +1,178 @@ +require_relative '../../../../test/unit/api/test_helper' + +class PushNotificationApiTest < ActiveSupport::TestCase + + def setup + login_api + environment = Environment.default + environment.enable_plugin(PushNotificationPlugin) + end + + should 'list all my device tokens' do + logged_user = @user + token1 = PushNotificationPlugin::DeviceToken.create!(:token => "firsttoken", device_name: "my device", :user => logged_user) + token2 = PushNotificationPlugin::DeviceToken.create!(:token => "secondtoken", device_name: "my device", :user => logged_user) + + get "/api/v1/push_notification_plugin/device_tokens?#{params.to_query}" + json = JSON.parse(last_response.body) + assert_equivalent [token1.token, token2.token], json + end + + should 'not list other people device tokens' do + user = User.create!(:login => 'outro', :email => 'outro@example.com', :password => 'outro', :password_confirmation => 'outro', :environment => Environment.default) + user.activate + PushNotificationPlugin::DeviceToken.create!(:token => "firsttoken", device_name: "my device", :user => user) + get "/api/v1/push_notification_plugin/device_tokens?#{params.merge(:target_id => user.id).to_query}" + assert_equal 401, last_response.status + end + + should 'admin see other user\'s device tokens' do + logged_user = @user + Environment.default.add_admin(logged_user.person) + logged_user.reload + + user = User.create!(:login => 'outro', :email => 'outro@example.com', :password => 'outro', :password_confirmation => 'outro', :environment => Environment.default) + user.activate + + token1 = PushNotificationPlugin::DeviceToken.create!(:token => "firsttoken", device_name: "my device", :user => user) + + get "/api/v1/push_notification_plugin/device_tokens?#{params.merge(:target_id => user.id).to_query}" + json = JSON.parse(last_response.body) + assert_equivalent [token1.token], json + end + +#------------------------------------------------------------------------------------------------------ + + should 'add my device token' do + params.merge!(:device_name => "my_device", :token => "token1") + post "/api/v1/push_notification_plugin/device_tokens?#{params.to_query}" + json = JSON.parse(last_response.body) + assert_equivalent ["token1"], json["user"]["device_tokens"] + end + + should 'not add device tokens for other people' do + user = User.create!(:login => 'outro', :email => 'outro@example.com', :password => 'outro', :password_confirmation => 'outro', :environment => Environment.default) + user.activate + params.merge!(:device_name => "my_device", :token => "tokenX", :target_id => user.id) + post "/api/v1/push_notification_plugin/device_tokens?#{params.to_query}" + assert_equal 401, last_response.status + end + + should 'admin add device tokens for other users' do + logged_user = @user + Environment.default.add_admin(logged_user.person) + logged_user.reload + + user = User.create!(:login => 'outro', :email => 'outro@example.com', :password => 'outro', :password_confirmation => 'outro', :environment => Environment.default) + user.activate + + params.merge!(:device_name => "my_device", :token=> "tokenY", :target_id => user.id) + post "/api/v1/push_notification_plugin/device_tokens?#{params.to_query}" + + json = JSON.parse(last_response.body) + assert_equivalent ["tokenY"], json["user"]["device_tokens"] + end + +#------------------------------------------------------------------------------------------------------ + + should 'delete my device tokens' do + logged_user = @user + PushNotificationPlugin::DeviceToken.create!(:token => "firsttoken", device_name: "my device", :user => logged_user) + PushNotificationPlugin::DeviceToken.create!(:token => "secondtoken", device_name: "my device", :user => logged_user) + + params.merge!(:token => "secondtoken") + delete "/api/v1/push_notification_plugin/device_tokens?#{params.to_query}" + json = JSON.parse(last_response.body) + assert_equivalent ["firsttoken"], json["user"]["device_tokens"] + end + + should 'not delete device tokens for other people' do + user = User.create!(:login => 'outro', :email => 'outro@example.com', :password => 'outro', :password_confirmation => 'outro', :environment => Environment.default) + user.activate + + PushNotificationPlugin::DeviceToken.create!(:token => "secondtoken", device_name: "my device", :user => user) + user.reload + + params.merge!(:token => "secondtoken", :target_id => user.id) + delete "/api/v1/push_notification_plugin/device_tokens?#{params.to_query}" + assert_equal 401, last_response.status + assert_equivalent user.device_token_list, ["secondtoken"] + end + + should 'admin delete device tokens for other users' do + logged_user = @user + Environment.default.add_admin(logged_user.person) + logged_user.reload + + user = User.create!(:login => 'outro', :email => 'outro@example.com', :password => 'outro', :password_confirmation => 'outro', :environment => Environment.default) + user.activate + + PushNotificationPlugin::DeviceToken.create!(:token => "firsttoken", device_name: "my device", :user => user) + PushNotificationPlugin::DeviceToken.create!(:token => "secondtoken", device_name: "my device", :user => user) + user.reload + + params.merge!(:token=> "secondtoken", :target_id => user.id) + delete "/api/v1/push_notification_plugin/device_tokens?#{params.to_query}" + + json = JSON.parse(last_response.body) + assert_equivalent ["firsttoken"], json["user"]["device_tokens"] + end + +#-------------------------------------------------------------------------------------------------------------------------------------------- + + should 'list all notifications disabled by default for new users' do + get "/api/v1/push_notification_plugin/notification_settings?#{params.to_query}" + json = JSON.parse(last_response.body) + json["user"]["notification_settings"].each_pair do |notification, status| + refute status + end + end + + should 'list device tokens notification options' do + logged_user = @user + logged_user.notification_settings.activate_notification "new_comment" + logged_user.save! + + get "/api/v1/push_notification_plugin/notification_settings?#{params.to_query}" + json = JSON.parse(last_response.body) + assert_equal true, json["user"]["notification_settings"]["new_comment"] + assert_equal false, json["user"]["notification_settings"]["add_friend"] + end + + should 'get possible notifications' do + get "/api/v1/push_notification_plugin/possible_notifications?#{params.to_query}" + json = JSON.parse(last_response.body) + assert_equivalent PushNotificationPlugin::NotificationSettings::NOTIFICATIONS.keys, json["possible_notifications"] + end + + should 'change device tokens notification options' do + logged_user = @user + params.merge!("new_comment"=> "true") + + post "/api/v1/push_notification_plugin/notification_settings?#{params.to_query}" + logged_user.reload + json = JSON.parse(last_response.body) + assert_equal true, json["user"]["notification_settings"]["new_comment"] + assert_equal true, logged_user.notification_settings.hash_flags["new_comment"] + end + + should 'get active notifications list' do + logged_user = @user + logged_user.notification_settings.activate_notification "new_comment" + logged_user.save! + + get "/api/v1/push_notification_plugin/active_notifications?#{params.to_query}" + json = JSON.parse(last_response.body) + assert_equivalent ["new_comment"], json + end + + should 'get inactive notifications list' do + logged_user = @user + logged_user.notification_settings.activate_notification "new_comment" + logged_user.save + + get "/api/v1/push_notification_plugin/inactive_notifications?#{params.to_query}" + json = JSON.parse(last_response.body) + assert_equivalent (PushNotificationPlugin::NotificationSettings::NOTIFICATIONS.keys-["new_comment"]), json + end +end diff --git a/plugins/push_notification/test/unit/notification_settings_test.rb b/plugins/push_notification/test/unit/notification_settings_test.rb new file mode 100644 index 0000000..a659551 --- /dev/null +++ b/plugins/push_notification/test/unit/notification_settings_test.rb @@ -0,0 +1,102 @@ +require_relative '../../lib/push_notification_helper.rb' +require 'test_helper' + +class NotificationSettingsTest < ActiveSupport::TestCase + include PushNotificationHelper + + should 'list notifications options in a hash' do + user = User.create!(:login => 'homer', :email => 'homer@example.com', :password => 'beer', :password_confirmation => 'beer', :environment => Environment.default) + user.activate + settings = PushNotificationPlugin::NotificationSettings.create!(:user => user) + user.reload + assert_equivalent PushNotificationPlugin::NotificationSettings::NOTIFICATIONS.keys, settings.hash_flags.keys + + settings.hash_flags.each_pair do |notification, status| + assert !!status == status + end + end + + should 'all notifications be disabled by default for new settingss' do + user = User.create!(:login => 'outro', :email => 'outro@example.com', :password => 'outro', :password_confirmation => 'outro', :environment => Environment.default) + user.activate + settings = PushNotificationPlugin::NotificationSettings.create!(:user => user) + settings.hash_flags.each_pair do |notification, status| + refute status + end + end + + should 'activate a notification for a settings' do + user = User.create!(:login => 'homer', :email => 'homer@example.com', :password => 'beer', :password_confirmation => 'beer', :environment => Environment.default) + user.activate + settings = PushNotificationPlugin::NotificationSettings.create!(:user => user) + settings.activate_notification "new_comment" + settings.save! + + assert_equal true, settings.hash_flags["new_comment"] + end + + should 'deactivate a notification for a settings' do + user = User.create!(:login => 'homer', :email => 'homer@example.com', :password => 'beer', :password_confirmation => 'beer', :environment => Environment.default) + user.activate + settings = PushNotificationPlugin::NotificationSettings.create!(:user => user) + + settings.activate_notification "new_comment" + settings.save! + assert_equal true, settings.hash_flags["new_comment"] + + settings.deactivate_notification "new_comment" + settings.save! + assert_equal false, settings.hash_flags["new_comment"] + end + + should 'set notification to specific state' do + user = User.create!(:login => 'homer', :email => 'homer@example.com', :password => 'beer', :password_confirmation => 'beer', :environment => Environment.default) + user.activate + settings = PushNotificationPlugin::NotificationSettings.new(:user => user) + + settings.set_notification_state "new_comment", 1 + settings.save! + assert_equal true, settings.hash_flags["new_comment"] + + settings.set_notification_state "new_comment", 0 + settings.save! + assert_equal false, settings.hash_flags["new_comment"] + end + + should 'check if notification is active' do + user = User.create!(:login => 'homer', :email => 'homer@example.com', :password => 'beer', :password_confirmation => 'beer', :environment => Environment.default) + user.activate + settings = PushNotificationPlugin::NotificationSettings.create!(:user => user) + + settings.activate_notification "new_comment" + settings.save! + + assert_equal true, settings.active?("new_comment") + end + + should 'list active notifications' do + user = User.create!(:login => 'homer', :email => 'homer@example.com', :password => 'beer', :password_confirmation => 'beer', :environment => Environment.default) + user.activate + settings = PushNotificationPlugin::NotificationSettings.create!(:user => user) + + settings.activate_notification "new_comment" + settings.save! + assert_equivalent ["new_comment"], settings.active_notifications + + settings.activate_notification "add_friend" + settings.save! + assert_equivalent ["new_comment","add_friend"], settings.active_notifications + end + + should 'list inactive notifications' do + user = User.create!(:login => 'homer', :email => 'homer@example.com', :password => 'beer', :password_confirmation => 'beer', :environment => Environment.default) + user.activate + settings = PushNotificationPlugin::NotificationSettings.create!(:user => user) + + assert_equivalent PushNotificationPlugin::NotificationSettings::NOTIFICATIONS.keys, settings.inactive_notifications + + settings.activate_notification "new_comment" + settings.save! + assert_equivalent (PushNotificationPlugin::NotificationSettings::NOTIFICATIONS.keys-["new_comment"]), settings.inactive_notifications + end +end diff --git a/plugins/push_notification/test/unit/observers_test.rb b/plugins/push_notification/test/unit/observers_test.rb new file mode 100644 index 0000000..c74102e --- /dev/null +++ b/plugins/push_notification/test/unit/observers_test.rb @@ -0,0 +1,86 @@ +require 'test_helper' +require_relative '../helpers/observers_test_helper' + +class ObserversTest < ActiveSupport::TestCase + include ObserversTestHelper + + def setup + environment = Environment.default + environment.enable_plugin(PushNotificationPlugin) + end + + should 'send notification when creating a comment' do + PushNotificationPlugin.any_instance.expects(:send_to_users) + person = fast_create(Person) + article = fast_create(Article, :profile_id => person.id) + Comment.create!(:author => person, :title => 'test comment', :body => 'body!', :source => article) + end + + should 'send notification when adding a friend' do + PushNotificationPlugin.any_instance.expects(:send_to_users) + create_add_friend_task + end + + should 'send notification when friendship is accepted' do + PushNotificationPlugin.any_instance.expects(:send_to_users).twice + create_add_friend_task.finish + end + + should 'send notification when friendship is refused' do + PushNotificationPlugin.any_instance.expects(:send_to_users).twice + create_add_friend_task.cancel + end + + should 'send notification when adding a member to a community' do + PushNotificationPlugin.any_instance.expects(:send_to_users) + create_add_member_task + end + + should 'send notification when accepting a member in a community' do + PushNotificationPlugin.any_instance.expects(:send_to_users).twice + create_add_member_task.finish + end + + should 'send notification when rejecting a member in a community' do + PushNotificationPlugin.any_instance.expects(:send_to_users).twice + create_add_member_task.cancel + end + + should 'send notification when suggesting an article' do + PushNotificationPlugin.any_instance.expects(:send_to_users).twice + create_suggest_article_task + end + + should 'send notification when accepting suggested article' do + PushNotificationPlugin.any_instance.expects(:send_to_users).times(5) + create_suggest_article_task.finish + end + + should 'send notification when rejecting suggested article' do + PushNotificationPlugin.any_instance.expects(:send_to_users).times(4) + create_suggest_article_task.cancel + end + + should 'send notification when an article needs to be approved' do + PushNotificationPlugin.any_instance.expects(:send_to_users) + create_approve_article_task + end + + should 'send notification when an article is approved' do + PushNotificationPlugin.any_instance.expects(:send_to_users).times(3) + create_approve_article_task.finish + end + + should 'send notification when an article is not approved' do + PushNotificationPlugin.any_instance.expects(:send_to_users).times(2) + create_approve_article_task.cancel + end + + should 'send notification when an article is created' do + PushNotificationPlugin.any_instance.expects(:send_to_users).twice + community = fast_create(Community) + person = fast_create(Person) + Article.create!(:name => 'great article', :profile => community) + Article.create!(:name => 'great article', :profile => person) + end +end diff --git a/plugins/push_notification/test/unit/push_notification_helper_test.rb b/plugins/push_notification/test/unit/push_notification_helper_test.rb new file mode 100644 index 0000000..6c92f3b --- /dev/null +++ b/plugins/push_notification/test/unit/push_notification_helper_test.rb @@ -0,0 +1,38 @@ +require_relative '../../lib/push_notification_helper.rb' +require 'test_helper' + +class PushNotificationHelperTest < ActiveSupport::TestCase + include PushNotificationHelper + + should 'get all tokens for a group of users' do + user = User.create!(:login => 'homer', :email => 'homer@example.com', :password => 'beer', :password_confirmation => 'beer', :environment => Environment.default) + user.activate + PushNotificationPlugin::DeviceToken.create!(:token => "tokenHomer1", device_name: "my device", :user => user) + PushNotificationPlugin::DeviceToken.create!(:token => "tokenHomer2", device_name: "my device", :user => user) + + user2 = User.create!(:login => 'bart', :email => 'bart@example.com', :password => 'fart', :password_confirmation => 'fart', :environment => Environment.default) + user2.activate + PushNotificationPlugin::DeviceToken.create!(:token => "tokenBart1", device_name: "my device", :user => user2) + PushNotificationPlugin::DeviceToken.create!(:token => "tokenBart2", device_name: "my device", :user => user2) + PushNotificationPlugin::DeviceToken.create!(:token => "tokenBart3", device_name: "my device", :user => user2) + + tokens = tokens_for_users([user,user2]) + + assert_equivalent ["tokenHomer1","tokenHomer2","tokenBart1","tokenBart2","tokenBart3"], tokens + end + + should 'filter users registered for a notification' do + user = User.create!(:login => 'homer', :email => 'homer@example.com', :password => 'beer', :password_confirmation => 'beer', :environment => Environment.default) + user.activate + user2 = User.create!(:login => 'bart', :email => 'bart@example.com', :password => 'fart', :password_confirmation => 'fart', :environment => Environment.default) + user2.activate + + user.notification_settings.activate_notification "new_comment" + user.save! + + users = filter_users_for_flag("new_comment",[user,user2]) + + assert_equivalent [user], users + end + +end diff --git a/plugins/push_notification/test/unit/push_notification_plugin_test.rb b/plugins/push_notification/test/unit/push_notification_plugin_test.rb new file mode 100644 index 0000000..1c79ea0 --- /dev/null +++ b/plugins/push_notification/test/unit/push_notification_plugin_test.rb @@ -0,0 +1,68 @@ +require_relative '../../lib/push_notification_helper.rb' +require 'test_helper' + +class PushNotificationPluginTest < ActiveSupport::TestCase + include PushNotificationHelper + + def setup + environment = Environment.default + environment.enable_plugin(PushNotificationPlugin) + end + + should 'subscribe and unsubscribe to notification' do + class AnyClass + def self.push_notification_new_comment_additional_users + ['YO'] + end + end + + assert PushNotificationPlugin::subscribe(Environment.default, "new_comment", AnyClass) + assert_equivalent PushNotificationPlugin::subscribers(Environment.default, "new_comment"), [AnyClass.name.constantize] + assert PushNotificationPlugin::unsubscribe(Environment.default, "new_comment", AnyClass) + assert_empty PushNotificationPlugin::subscribers(Environment.default, "new_comment") + end + + should 'get additional users from subscribers' do + class AnyClass + def self.push_notification_new_comment_additional_users + ['YO'] + end + end + + PushNotificationPlugin::subscribe(Environment.default, "new_comment", AnyClass) + AnyClass.expects(:push_notification_new_comment_additional_users).returns(['YO']) + subscribers_additional_users("new_comment", Environment.default) + end + + should 'return nill for unknown notification subscription methods' do + class AnyEventCallbackClass + def self.push_notification_any_event_additional_users + ['YO'] + end + end + + assert_nil PushNotificationPlugin::subscribe(Environment.default, "any_event", AnyEventCallbackClass) + assert_nil PushNotificationPlugin::unsubscribe(Environment.default, "any_event", AnyEventCallbackClass) + assert_nil PushNotificationPlugin::subscribers(Environment.default, "any_event") + end + + should 'return empty list for known notification without subscribers' do + class CommentCallbackClass + def self.push_notification_new_comment_additional_users + ['YO'] + end + end + + refute PushNotificationPlugin::unsubscribe(Environment.default, "new_comment", CommentCallbackClass) + assert_empty PushNotificationPlugin::subscribers(Environment.default, "new_comment") + end + + should 'not subscribe to notification if correspondent method callback is not implemented' do + class NoCallbackClass + end + + assert_nil PushNotificationPlugin::subscribe(Environment.default, "new_comment", NoCallbackClass) + assert_empty PushNotificationPlugin::subscribers(Environment.default, "new_comment") + end + +end diff --git a/plugins/push_notification/views/_notification_events_form.html.erb b/plugins/push_notification/views/_notification_events_form.html.erb new file mode 100644 index 0000000..e79efc8 --- /dev/null +++ b/plugins/push_notification/views/_notification_events_form.html.erb @@ -0,0 +1,17 @@ + + + + + + + <% settings.each do |event, checked|%> + + + + + <% end %> + +
<%= t("push_notification_plugin.controllers.myprofile.event") %> <%= t("push_notification_plugin.controllers.myprofile.enabled") %>
<%= t("push_notification_plugin.events.#{event}") %> + <%= hidden_field_tag "settings[#{event}]", "0" %> + <%= check_box_tag "settings[#{event}]", "1", (checked == "1" || checked == true) %> +
diff --git a/plugins/push_notification/views/push_notification_plugin_admin/_notification_events_form.html.erb b/plugins/push_notification/views/push_notification_plugin_admin/_notification_events_form.html.erb new file mode 120000 index 0000000..7cb6222 --- /dev/null +++ b/plugins/push_notification/views/push_notification_plugin_admin/_notification_events_form.html.erb @@ -0,0 +1 @@ +../_notification_events_form.html.erb \ No newline at end of file diff --git a/plugins/push_notification/views/push_notification_plugin_admin/index.html.erb b/plugins/push_notification/views/push_notification_plugin_admin/index.html.erb new file mode 100644 index 0000000..712ffbc --- /dev/null +++ b/plugins/push_notification/views/push_notification_plugin_admin/index.html.erb @@ -0,0 +1,14 @@ +

<%= t("push_notification_plugin.lib.plugin.settings")%>

+ +<%= form_for(:server_settings, :url => {:action => 'update'}) do |f| %> + + <%= labelled_form_field t('push_notification_plugin.controllers.admin.server_api_key'), f.text_field(:server_api_key, :size => "80") %> + +

<%= t('push_notification_plugin.lib.plugin.enabled_notifications')%>

+ <%= render partial: "notification_events_form", locals:{f: f, settings: @settings} %> + + <% button_bar do %> + <%= submit_button(:save, c_('Save'), :cancel => {:controller => 'plugins'}) %> + <% end %> + +<% end %> diff --git a/plugins/push_notification/views/push_notification_plugin_myprofile/_notification_events_form.html.erb b/plugins/push_notification/views/push_notification_plugin_myprofile/_notification_events_form.html.erb new file mode 120000 index 0000000..7cb6222 --- /dev/null +++ b/plugins/push_notification/views/push_notification_plugin_myprofile/_notification_events_form.html.erb @@ -0,0 +1 @@ +../_notification_events_form.html.erb \ No newline at end of file diff --git a/plugins/push_notification/views/push_notification_plugin_myprofile/index.html.erb b/plugins/push_notification/views/push_notification_plugin_myprofile/index.html.erb new file mode 100644 index 0000000..4b2564b --- /dev/null +++ b/plugins/push_notification/views/push_notification_plugin_myprofile/index.html.erb @@ -0,0 +1,25 @@ +

<%= t("push_notification_plugin.controllers.myprofile.my_devices") %>

+ <% if @devices.blank? %> +
<%= t("push_notification_plugin.controllers.myprofile.no_devices")%>
+ <% else %> + + + + + + <% @devices.each do |d| %> + + + + + <% end %> +
<%= t("push_notification_plugin.lib.plugin.device")%><%= t("push_notification_plugin.lib.plugin.actions") %>
<%= d.device_name %><%= button_without_text(:delete, t('push_notification_plugin.controllers.myprofile.delete_device'), {:controller => "push_notification_plugin_myprofile", :action => "delete_device", :params => {:device => d}}, method: :delete, id: "delete_device_#{d.id}") %>
+ <% end %> +<%= form_for(:settings, :url => {:controller => "push_notification_plugin_myprofile", :action => "update_settings"}) do |f| %> +

<%= t('push_notification_plugin.lib.plugin.enabled_notifications') %>

+ <%= render partial: "notification_events_form", locals: {f: f, settings: @settings} %> + <% button_bar do %> + <%= submit_button(:save, _('Save')) %> + <%= button(:back, _('Back to control panel'), :controller => (profile.nil? ? 'admin_panel': 'profile_editor')) %> + <% end %> +<% end %> -- libgit2 0.21.2