diff --git a/.rubocop.yml b/.rubocop.yml index b3cc9d86d..d7b548944 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -201,6 +201,9 @@ Style/RedundantFreeze: Style/SymbolProc: Enabled: true +Style/SymbolArray: + Enabled: false + # Use `foo {}` not `foo{}`. Layout/SpaceBeforeBlockBraces: Enabled: true @@ -321,4 +324,18 @@ Performance/DeletePrefix: Enabled: true Performance/DeleteSuffix: - Enabled: true \ No newline at end of file + Enabled: true + +Metrics/ModuleLength: + Enabled: false + +Metrics/ClassLength: + Enabled: false + +Metrics/MethodLength: + Exclude: + - 'app/abilities/**/*' + +Metrics/AbcSize: + Exclude: + - 'app/abilities/**/*' \ No newline at end of file diff --git a/Gemfile b/Gemfile index c97d4a8eb..d24e6e02c 100644 --- a/Gemfile +++ b/Gemfile @@ -120,7 +120,7 @@ group :development, :docker_development do # Spring speeds up development by keeping your application running in the background. Read more: https://github.com/rails/spring gem "spring" gem "spring-watcher-listen", "~> 2.0.0" - gem "rubocop", "~> 0.93", require: false + gem "rubocop", "~> 1.50", require: false gem "rubocop-packaging", require: false gem "rubocop-performance", require: false gem "rubocop-rails", require: false diff --git a/app/abilities/administration_ability.rb b/app/abilities/administration_ability.rb index e1b6bb5f1..df640725d 100644 --- a/app/abilities/administration_ability.rb +++ b/app/abilities/administration_ability.rb @@ -12,4 +12,4 @@ def initialize(user) user.admin? end end -end \ No newline at end of file +end diff --git a/app/abilities/announcement_ability.rb b/app/abilities/announcement_ability.rb index ba171a55a..177f43510 100644 --- a/app/abilities/announcement_ability.rb +++ b/app/abilities/announcement_ability.rb @@ -14,7 +14,7 @@ def initialize(user) can :create, Announcement do |announcement| user.admin? || - (announcement.lecture.present? && user.can_edit?(announcement.lecture)) + (announcement.lecture.present? && user.can_edit?(announcement.lecture)) end end -end \ No newline at end of file +end diff --git a/app/abilities/answer_ability.rb b/app/abilities/answer_ability.rb index 11b610bba..f4b41b3c1 100644 --- a/app/abilities/answer_ability.rb +++ b/app/abilities/answer_ability.rb @@ -10,4 +10,4 @@ def initialize(user) can :update_answer_box, Answer end -end \ No newline at end of file +end diff --git a/app/abilities/assignment_ability.rb b/app/abilities/assignment_ability.rb index 28a85f2eb..2d39c7edd 100644 --- a/app/abilities/assignment_ability.rb +++ b/app/abilities/assignment_ability.rb @@ -9,4 +9,4 @@ def initialize(user) assignment.lecture.present? && user.can_edit?(assignment.lecture) end end -end \ No newline at end of file +end diff --git a/app/abilities/chapter_ability.rb b/app/abilities/chapter_ability.rb index fecdd083d..2d6cb94b8 100644 --- a/app/abilities/chapter_ability.rb +++ b/app/abilities/chapter_ability.rb @@ -9,4 +9,4 @@ def initialize(user) chapter.lecture.present? && user.can_edit?(chapter.lecture) end end -end \ No newline at end of file +end diff --git a/app/abilities/course_ability.rb b/app/abilities/course_ability.rb index ea3fba2de..5e3f1545a 100644 --- a/app/abilities/course_ability.rb +++ b/app/abilities/course_ability.rb @@ -20,4 +20,4 @@ def initialize(user) !user.generic? || course.subscribed_by?(user) end end -end \ No newline at end of file +end diff --git a/app/abilities/division_ability.rb b/app/abilities/division_ability.rb index 0c5339168..c1a38ca88 100644 --- a/app/abilities/division_ability.rb +++ b/app/abilities/division_ability.rb @@ -8,4 +8,4 @@ def initialize(user) user.admin? end end -end \ No newline at end of file +end diff --git a/app/abilities/erdbeere_ability.rb b/app/abilities/erdbeere_ability.rb index e0ed55d84..f19f32782 100644 --- a/app/abilities/erdbeere_ability.rb +++ b/app/abilities/erdbeere_ability.rb @@ -12,4 +12,4 @@ def initialize(user) !user.generic? end end -end \ No newline at end of file +end diff --git a/app/abilities/interaction_ability.rb b/app/abilities/interaction_ability.rb index c577971ba..b8dc9f41e 100644 --- a/app/abilities/interaction_ability.rb +++ b/app/abilities/interaction_ability.rb @@ -8,4 +8,4 @@ def initialize(user) user.admin? end end -end \ No newline at end of file +end diff --git a/app/abilities/item_ability.rb b/app/abilities/item_ability.rb index 7b0052cef..77960bb19 100644 --- a/app/abilities/item_ability.rb +++ b/app/abilities/item_ability.rb @@ -6,9 +6,9 @@ def initialize(user) can [:create, :update, :edit, :destroy], Item do |item| (item.medium.nil? && !user.generic?) || - (item.medium && user.can_edit?(item.medium)) + (item.medium && user.can_edit?(item.medium)) end can :display, Item end -end \ No newline at end of file +end diff --git a/app/abilities/lecture_ability.rb b/app/abilities/lecture_ability.rb index 5480923c4..4794e679c 100644 --- a/app/abilities/lecture_ability.rb +++ b/app/abilities/lecture_ability.rb @@ -36,4 +36,4 @@ def initialize(user) lecture.published? || !user.generic? end end -end \ No newline at end of file +end diff --git a/app/abilities/lesson_ability.rb b/app/abilities/lesson_ability.rb index 0ee9afabd..c1af01c98 100644 --- a/app/abilities/lesson_ability.rb +++ b/app/abilities/lesson_ability.rb @@ -12,4 +12,4 @@ def initialize(user) user.can_edit?(lesson) end end -end \ No newline at end of file +end diff --git a/app/abilities/main_ability.rb b/app/abilities/main_ability.rb index bd65084b6..7ced6bc32 100644 --- a/app/abilities/main_ability.rb +++ b/app/abilities/main_ability.rb @@ -4,4 +4,4 @@ class MainAbility def initialize(user) can :start, :main end -end \ No newline at end of file +end diff --git a/app/abilities/medium_ability.rb b/app/abilities/medium_ability.rb index 1a39a4d36..f173dd07c 100644 --- a/app/abilities/medium_ability.rb +++ b/app/abilities/medium_ability.rb @@ -8,8 +8,8 @@ def initialize(user) can [:index, :new, :search], Medium can [:show, :show_comments], Medium do |medium| - medium.visible_for_user?(user) && - !(medium.sort.in?(['Question', 'Remark']) && !user.can_edit?(medium)) + medium.visible_for_user?(user) && + !(medium.sort.in?(['Question', 'Remark']) && !user.can_edit?(medium)) end can :inspect, Medium do |medium| @@ -39,7 +39,7 @@ def initialize(user) # guest users can play/display media when their release status 'all' can [:play, :display, :geogebra], Medium do |medium| (!user.new_record? && medium.visible_for_user?(user)) || - medium.free? + medium.free? end can :update_tags, Medium do |medium| @@ -50,4 +50,4 @@ def initialize(user) !user.new_record? end end -end \ No newline at end of file +end diff --git a/app/abilities/notification_ability.rb b/app/abilities/notification_ability.rb index 89fe5d7d1..a3b9abd0c 100644 --- a/app/abilities/notification_ability.rb +++ b/app/abilities/notification_ability.rb @@ -11,4 +11,4 @@ def initialize(user) notification.recipient == user end end -end \ No newline at end of file +end diff --git a/app/abilities/profile_ability.rb b/app/abilities/profile_ability.rb index 9370bbc77..3000ca054 100644 --- a/app/abilities/profile_ability.rb +++ b/app/abilities/profile_ability.rb @@ -8,4 +8,4 @@ def initialize(user) :toggle_thread_subscription, :subscribe_lecture, :unsubscribe_lecture, :star_lecture, :unstar_lecture, :show_accordion, :request_data], :profile end -end \ No newline at end of file +end diff --git a/app/abilities/program_ability.rb b/app/abilities/program_ability.rb index 4b691a242..50c586533 100644 --- a/app/abilities/program_ability.rb +++ b/app/abilities/program_ability.rb @@ -8,4 +8,4 @@ def initialize(user) user.admin? end end -end \ No newline at end of file +end diff --git a/app/abilities/question_ability.rb b/app/abilities/question_ability.rb index c921d7fca..1d20d8d27 100644 --- a/app/abilities/question_ability.rb +++ b/app/abilities/question_ability.rb @@ -10,4 +10,4 @@ def initialize(user) user.can_edit?(question) end end -end \ No newline at end of file +end diff --git a/app/abilities/quiz_ability.rb b/app/abilities/quiz_ability.rb index 57372048e..4032d5125 100644 --- a/app/abilities/quiz_ability.rb +++ b/app/abilities/quiz_ability.rb @@ -13,4 +13,4 @@ def initialize(user) quiz && user.can_edit?(quiz.becomes(Medium)) end end -end \ No newline at end of file +end diff --git a/app/abilities/quiz_certificate_ability.rb b/app/abilities/quiz_certificate_ability.rb index 206827a25..31bca228f 100644 --- a/app/abilities/quiz_certificate_ability.rb +++ b/app/abilities/quiz_certificate_ability.rb @@ -10,4 +10,4 @@ def initialize(user) user.tutor? || !user.generic? end end -end \ No newline at end of file +end diff --git a/app/abilities/referral_ability.rb b/app/abilities/referral_ability.rb index 08bbd413e..138ba4df7 100644 --- a/app/abilities/referral_ability.rb +++ b/app/abilities/referral_ability.rb @@ -12,4 +12,4 @@ def initialize(user) !user.generic? || user.media_editor? end end -end \ No newline at end of file +end diff --git a/app/abilities/remark_ability.rb b/app/abilities/remark_ability.rb index 846798578..63b4810e7 100644 --- a/app/abilities/remark_ability.rb +++ b/app/abilities/remark_ability.rb @@ -8,4 +8,4 @@ def initialize(user) user.can_edit?(remark) end end -end \ No newline at end of file +end diff --git a/app/abilities/search_ability.rb b/app/abilities/search_ability.rb index 499fee1bc..018392d85 100644 --- a/app/abilities/search_ability.rb +++ b/app/abilities/search_ability.rb @@ -6,4 +6,4 @@ def initialize(user) can :index, :search end -end \ No newline at end of file +end diff --git a/app/abilities/section_ability.rb b/app/abilities/section_ability.rb index 1182aece4..215b19c34 100644 --- a/app/abilities/section_ability.rb +++ b/app/abilities/section_ability.rb @@ -13,4 +13,4 @@ def initialize(user) user.can_edit?(section.lecture) end end -end \ No newline at end of file +end diff --git a/app/abilities/subject_ability.rb b/app/abilities/subject_ability.rb index 9dd2944e3..4961d65ef 100644 --- a/app/abilities/subject_ability.rb +++ b/app/abilities/subject_ability.rb @@ -8,4 +8,4 @@ def initialize(user) user.admin? end end -end \ No newline at end of file +end diff --git a/app/abilities/submission_ability.rb b/app/abilities/submission_ability.rb index d3ffaeff1..5905c72a0 100644 --- a/app/abilities/submission_ability.rb +++ b/app/abilities/submission_ability.rb @@ -19,9 +19,9 @@ def initialize(user) end can [:show_manuscript, :show_correction], Submission do |submission| - user.in?(submission.users) || user.in?(submission.tutorial.tutors) || - user.in?(submission.tutorial.lecture.editors) || - user == submission.tutorial.lecture.teacher + user.in?(submission.users) || user.in?(submission.tutorial.tutors) || + user.in?(submission.tutorial.lecture.editors) || + user == submission.tutorial.lecture.teacher end end end diff --git a/app/abilities/tag_ability.rb b/app/abilities/tag_ability.rb index 0ddad1cf6..c04cd4c90 100644 --- a/app/abilities/tag_ability.rb +++ b/app/abilities/tag_ability.rb @@ -13,4 +13,3 @@ def initialize(user) end end end - diff --git a/app/abilities/term_ability.rb b/app/abilities/term_ability.rb index 6daba388d..51a20e8af 100644 --- a/app/abilities/term_ability.rb +++ b/app/abilities/term_ability.rb @@ -9,4 +9,4 @@ def initialize(user) user.admin? end end -end \ No newline at end of file +end diff --git a/app/abilities/tutorial_ability.rb b/app/abilities/tutorial_ability.rb index d17014db1..fa6087ac6 100644 --- a/app/abilities/tutorial_ability.rb +++ b/app/abilities/tutorial_ability.rb @@ -20,7 +20,7 @@ def initialize(user) can [:bulk_download_submissions, :bulk_download_corrections, :bulk_upload, :export_teams], Tutorial do |tutorial| user.in?(tutorial.tutors) || - user.editor_or_teacher_in?(tutorial.lecture) + user.editor_or_teacher_in?(tutorial.lecture) end can :validate_certificate, Tutorial do @@ -28,7 +28,3 @@ def initialize(user) end end end - - - - diff --git a/app/abilities/watchlist_ability.rb b/app/abilities/watchlist_ability.rb index 67fb84bec..9b7c89044 100644 --- a/app/abilities/watchlist_ability.rb +++ b/app/abilities/watchlist_ability.rb @@ -20,4 +20,4 @@ def initialize(user) entries.all? { |entry| entry&.in?(watchlist.watchlist_entries) } end end -end \ No newline at end of file +end diff --git a/app/controllers/announcements_controller.rb b/app/controllers/announcements_controller.rb index 454cb6e03..ba3492fff 100644 --- a/app/controllers/announcements_controller.rb +++ b/app/controllers/announcements_controller.rb @@ -55,49 +55,50 @@ def expel private - def announcement_params - params.require(:announcement).permit(:details, :lecture_id, :on_main_page) - end + def announcement_params + params.require(:announcement).permit(:details, :lecture_id, :on_main_page) + end - def create_notifications - users_to_notify = if @announcement.lecture.present? - @announcement.lecture.users - else - User - end - notifications = [] - users_to_notify.update_all(updated_at: Time.now) - users_to_notify.find_each do |u| - notifications << Notification.new(recipient: u, - notifiable_id: @announcement.id, - notifiable_type: 'Announcement', - action: 'create') + def create_notifications + users_to_notify = if @announcement.lecture.present? + @announcement.lecture.users + else + User + end + notifications = [] + users_to_notify.update_all(updated_at: Time.now) + users_to_notify.find_each do |u| + notifications << Notification.new(recipient: u, + notifiable_id: @announcement.id, + notifiable_type: 'Announcement', + action: 'create') + end + # use activerecord-import gem to use only one SQL instruction + Notification.import notifications end - # use activerecord-import gem to use only one SQL instruction - Notification.import notifications - end - def send_notification_email - recipients = if @announcement.lecture.present? - @announcement.lecture.users - .where(email_for_announcement: true) - else - User.where(email_for_news: true) - end - I18n.available_locales.each do |l| - local_recipients = recipients.where(locale: l) - if local_recipients.any? - NotificationMailer.with(recipients: local_recipients.pluck(:id), - locale: l, - announcement: @announcement) - .announcement_email.deliver_later + def send_notification_email + recipients = if @announcement.lecture.present? + @announcement.lecture.users + .where(email_for_announcement: true) + else + User.where(email_for_news: true) + end + I18n.available_locales.each do |l| + local_recipients = recipients.where(locale: l) + if local_recipients.any? + NotificationMailer.with(recipients: local_recipients.pluck(:id), + locale: l, + announcement: @announcement) + .announcement_email.deliver_later + end end end - end - def set_announcement - @announcement = Announcement.find_by_id(params[:id]) - return if @announcement.present? - redirect_to :root, alert: I18n.t('controllers.no_announcement') - end + def set_announcement + @announcement = Announcement.find_by_id(params[:id]) + return if @announcement.present? + + redirect_to :root, alert: I18n.t('controllers.no_announcement') + end end diff --git a/app/controllers/answers_controller.rb b/app/controllers/answers_controller.rb index 0b5ec5916..8e0441d4d 100644 --- a/app/controllers/answers_controller.rb +++ b/app/controllers/answers_controller.rb @@ -19,6 +19,7 @@ def create authorize! :create, @answer I18n.locale = @answer.question&.locale_with_inheritance return unless @answer.save + @success = true end @@ -29,6 +30,7 @@ def update def destroy @id = params[:id] return unless @answer.destroy + @success = true end @@ -39,13 +41,14 @@ def update_answer_box private - def set_answer - @answer = Answer.find_by_id(params[:id]) - return if @answer.present? - redirect_to root_path, alert: I18n.t('controllers.no_answer') - end + def set_answer + @answer = Answer.find_by_id(params[:id]) + return if @answer.present? - def answer_params - params.require(:answer).permit(:text, :value, :explanation, :question_id) - end + redirect_to root_path, alert: I18n.t('controllers.no_answer') + end + + def answer_params + params.require(:answer).permit(:text, :value, :explanation, :question_id) + end end diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index 76550fadb..a19c8553f 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -9,13 +9,13 @@ class ApplicationController < ActionController::Base before_action :set_locale after_action :store_interaction, if: :user_signed_in? - etag { current_user.try :id } def current_user - unless controller_name == 'administration' && action_name == 'index' + unless controller_name == 'administration' && action_name == 'index' return super end + @current_user ||= super.tap do |user| ::ActiveRecord::Associations::Preloader.new(records: [user], associations: @@ -46,7 +46,7 @@ def current_user rescue_from ActionController::InvalidAuthenticityToken do redirect_to main_app.root_url, - alert: I18n.t('controllers.session_expired') + alert: I18n.t('controllers.session_expired') end # determine where to send the user after login @@ -69,65 +69,69 @@ def prevent_caching protected - def configure_permitted_parameters - # add additional paramters to registration - devise_parameter_sanitizer.permit(:sign_up, keys: [:locale, :consents]) - end + def configure_permitted_parameters + # add additional paramters to registration + devise_parameter_sanitizer.permit(:sign_up, keys: [:locale, :consents]) + end private - # It's important that the location is NOT stored if: - # - The request method is not GET (non idempotent) - # - The request is handled by a Devise controller such as - # Devise::SessionsController as that could cause an - # infinite redirect loop. - # - The request is an Ajax request as this can lead to very - # unexpected behaviour. - def storable_location? - request.get? && is_navigational_format? && !devise_controller? && - !request.xhr? - end + # It's important that the location is NOT stored if: + # - The request method is not GET (non idempotent) + # - The request is handled by a Devise controller such as + # Devise::SessionsController as that could cause an + # infinite redirect loop. + # - The request is an Ajax request as this can lead to very + # unexpected behaviour. + def storable_location? + request.get? && is_navigational_format? && !devise_controller? && + !request.xhr? + end - def store_user_location! - # :user is the scope we are authenticating - store_location_for(:user, request.fullpath) - end + def store_user_location! + # :user is the scope we are authenticating + store_location_for(:user, request.fullpath) + end - def set_locale - I18n.locale = current_user.try(:locale) || locale_param || + def set_locale + I18n.locale = current_user.try(:locale) || locale_param || cookie_locale_param || I18n.default_locale - unless user_signed_in? - cookies[:locale] = I18n.locale + unless user_signed_in? + cookies[:locale] = I18n.locale + end end - end - def store_interaction - return if controller_name.in?(['sessions', 'administration', 'users', - 'events', 'interactions', 'profile', - 'clickers', 'clicker_votes', 'registrations']) - return if controller_name == 'main' && action_name == 'home' - return if controller_name == 'tags' && action_name.in?(['fill_tag_select', 'fill_course_tags']) - study_participant = current_user.anonymized_id if current_user.study_participant - # as of Rack 2.0.8, the session_id is wrapped in a class of its own - # it is not a string anymore - # see https://github.com/rack/rack/issues/1433 - InteractionSaver.perform_async(request.session_options[:id].public_id, - request.original_fullpath, - request.referrer, - study_participant) - end + def store_interaction + return if controller_name.in?(['sessions', 'administration', 'users', + 'events', 'interactions', 'profile', + 'clickers', 'clicker_votes', 'registrations']) + return if controller_name == 'main' && action_name == 'home' + return if controller_name == 'tags' && action_name.in?(['fill_tag_select', + 'fill_course_tags']) + + study_participant = current_user.anonymized_id if current_user.study_participant + # as of Rack 2.0.8, the session_id is wrapped in a class of its own + # it is not a string anymore + # see https://github.com/rack/rack/issues/1433 + InteractionSaver.perform_async(request.session_options[:id].public_id, + request.original_fullpath, + request.referrer, + study_participant) + end - def locale_param - return unless params[:locale].in?(available_locales) - params[:locale] - end + def locale_param + return unless params[:locale].in?(available_locales) - def cookie_locale_param - return unless cookies[:locale].in?(available_locales) - cookies[:locale] - end + params[:locale] + end - def available_locales - I18n.available_locales.map(&:to_s) - end + def cookie_locale_param + return unless cookies[:locale].in?(available_locales) + + cookies[:locale] + end + + def available_locales + I18n.available_locales.map(&:to_s) + end end diff --git a/app/controllers/assignments_controller.rb b/app/controllers/assignments_controller.rb index 6dd2a2ad3..9f2b97f09 100644 --- a/app/controllers/assignments_controller.rb +++ b/app/controllers/assignments_controller.rb @@ -32,6 +32,7 @@ def update @assignment.update(assignment_params) @errors = @assignment.errors return if @errors.present? + @assignment.update(medium: nil) if assignment_params[:medium_id].blank? end @@ -52,27 +53,29 @@ def cancel_new private - def set_assignment - @assignment = Assignment.find_by_id(params[:id]) - @lecture = @assignment&.lecture - set_assignment_locale and return if @assignment - redirect_to :root, alert: I18n.t('controllers.no_assignment') - end + def set_assignment + @assignment = Assignment.find_by_id(params[:id]) + @lecture = @assignment&.lecture + set_assignment_locale and return if @assignment - def set_lecture - @lecture = Lecture.find_by_id(assignment_params[:lecture_id]) - return if @lecture - redirect_to :root, alert: I18n.t('controllers.no_lecture') - end + redirect_to :root, alert: I18n.t('controllers.no_assignment') + end + + def set_lecture + @lecture = Lecture.find_by_id(assignment_params[:lecture_id]) + return if @lecture - def set_assignment_locale - I18n.locale = @lecture&.locale_with_inheritance || current_user.locale || + redirect_to :root, alert: I18n.t('controllers.no_lecture') + end + + def set_assignment_locale + I18n.locale = @lecture&.locale_with_inheritance || current_user.locale || I18n.default_locale - end + end - def assignment_params - params.require(:assignment).permit(:title, :medium_id, :lecture_id, - :deadline, :accepted_file_type, - :deletion_date) - end -end \ No newline at end of file + def assignment_params + params.require(:assignment).permit(:title, :medium_id, :lecture_id, + :deadline, :accepted_file_type, + :deletion_date) + end +end diff --git a/app/controllers/chapters_controller.rb b/app/controllers/chapters_controller.rb index 60e3ce7ab..1b6188a3c 100644 --- a/app/controllers/chapters_controller.rb +++ b/app/controllers/chapters_controller.rb @@ -15,7 +15,7 @@ def edit def update I18n.locale = @chapter.lecture.locale_with_inheritance || - current_user.locale || I18n.default_locale + current_user.locale || I18n.default_locale @chapter.update(chapter_params) if @chapter.valid? predecessor = params[:chapter][:predecessor] @@ -35,7 +35,7 @@ def create @chapter = Chapter.new(chapter_params) authorize! :create, @chapter I18n.locale = @chapter&.lecture&.locale_with_inheritance || - current_user.locale || I18n.default_locale + current_user.locale || I18n.default_locale position = params[:chapter][:predecessor] # place the chapter in the correct position if position.present? @@ -52,7 +52,7 @@ def new @chapter = Chapter.new(lecture: @lecture) authorize! :new, @chapter I18n.locale = @chapter.lecture.locale_with_inheritance || - current_user.locale || I18n.default_locale + current_user.locale || I18n.default_locale end def destroy @@ -68,19 +68,20 @@ def list_sections private - def set_chapter - @chapter = Chapter.find_by_id(params[:id]) - return if @chapter.present? - redirect_to :root, alert: I18n.t('controllers.no_chapter') - end + def set_chapter + @chapter = Chapter.find_by_id(params[:id]) + return if @chapter.present? - def chapter_params - params.require(:chapter).permit(:title, :display_number, :lecture_id, - :hidden, :details) - end + redirect_to :root, alert: I18n.t('controllers.no_chapter') + end - def set_view_locale - I18n.locale = @chapter.lecture.locale_with_inheritance || + def chapter_params + params.require(:chapter).permit(:title, :display_number, :lecture_id, + :hidden, :details) + end + + def set_view_locale + I18n.locale = @chapter.lecture.locale_with_inheritance || current_user.locale || I18n.default_locale - end + end end diff --git a/app/controllers/clicker_votes_controller.rb b/app/controllers/clicker_votes_controller.rb index 5ed76326b..d2b684e04 100644 --- a/app/controllers/clicker_votes_controller.rb +++ b/app/controllers/clicker_votes_controller.rb @@ -16,7 +16,7 @@ def create private - def vote_params - params.require(:clicker_vote).permit(:clicker_id, :value) - end + def vote_params + params.require(:clicker_vote).permit(:clicker_id, :value) + end end diff --git a/app/controllers/clickers_controller.rb b/app/controllers/clickers_controller.rb index e517ff421..55ced38b8 100644 --- a/app/controllers/clickers_controller.rb +++ b/app/controllers/clickers_controller.rb @@ -112,20 +112,21 @@ def render_clickerizable_actions private - def clicker_params - params.require(:clicker).permit(:editor_id, :teachable_type, - :teachable_id, :question_id, :title) - end + def clicker_params + params.require(:clicker).permit(:editor_id, :teachable_type, + :teachable_id, :question_id, :title) + end - def code_params - params.permit(:code) - end + def code_params + params.permit(:code) + end - def set_clicker - @clicker = Clicker.find_by_id(params[:id]) - @code = user_signed_in? ? nil : @clicker&.code - @entered_code = code_params[:code] - return if @clicker - redirect_to :root, alert: I18n.t('controllers.no_clicker') - end -end \ No newline at end of file + def set_clicker + @clicker = Clicker.find_by_id(params[:id]) + @code = user_signed_in? ? nil : @clicker&.code + @entered_code = code_params[:code] + return if @clicker + + redirect_to :root, alert: I18n.t('controllers.no_clicker') + end +end diff --git a/app/controllers/confirmations_controller.rb b/app/controllers/confirmations_controller.rb index 8acec71a3..b0fd5e1ed 100644 --- a/app/controllers/confirmations_controller.rb +++ b/app/controllers/confirmations_controller.rb @@ -1,9 +1,8 @@ class ConfirmationsController < Devise::ConfirmationsController - private - def after_confirmation_path_for(resource_name, resource) - sign_in(resource) # In case you want to sign in the user - edit_profile_path - end -end \ No newline at end of file + def after_confirmation_path_for(resource_name, resource) + sign_in(resource) # In case you want to sign in the user + edit_profile_path + end +end diff --git a/app/controllers/courses_controller.rb b/app/controllers/courses_controller.rb index bc28a96c8..67be24754 100644 --- a/app/controllers/courses_controller.rb +++ b/app/controllers/courses_controller.rb @@ -21,6 +21,7 @@ def update @course.update(course_params) @errors = @course.errors return unless @errors.empty? + @course.update(image: nil) if params[:course][:detach_image] == 'true' changed_image = @course.image_data != old_image_data if @course.image.present? && changed_image @@ -56,7 +57,8 @@ def destroy def take_random_quiz tags = Tag.where(id: random_quiz_params[:search_course_tag_ids]) - random_quiz = @course.create_random_quiz!(tags, random_quiz_params[:random_quiz_count].to_i) + random_quiz = @course.create_random_quiz!(tags, + random_quiz_params[:random_quiz_count].to_i) redirect_to take_quiz_path(random_quiz) end @@ -72,70 +74,73 @@ def search results = search.results @total = search.total @courses = Kaminari.paginate_array(results, total_count: @total) - .page(params[:page]).per(search_params[:per]) + .page(params[:page]).per(search_params[:per]) end private - def set_course - @course = Course.find_by_id(params[:id]) - return if @course.present? - redirect_to :root, alert: I18n.t('controllers.no_course') - end + def set_course + @course = Course.find_by_id(params[:id]) + return if @course.present? - def set_course_admin - @course = Course.find_by_id(params[:id]) - return if @course.present? - redirect_to administration_path - end + redirect_to :root, alert: I18n.t('controllers.no_course') + end - def course_params - params.require(:course).permit(:title, :short_title, :organizational, - :organizational_concept, :locale, - :term_independent, :image, - tag_ids: [], - preceding_course_ids: [], - editor_ids: [], - division_ids: []) - end + def set_course_admin + @course = Course.find_by_id(params[:id]) + return if @course.present? - def tag_params - params.permit(:count, tag_ids: []) - end + redirect_to administration_path + end - def search_params - params.require(:search).permit(:all_editors, :all_programs, :fulltext, - :term_independent, :per, - editor_ids: [], - program_ids: []) - end + def course_params + params.require(:course).permit(:title, :short_title, :organizational, + :organizational_concept, :locale, + :term_independent, :image, + tag_ids: [], + preceding_course_ids: [], + editor_ids: [], + division_ids: []) + end - def random_quiz_params - params.require(:quiz).permit(:random_quiz_count, - search_course_tag_ids: []) - end + def tag_params + params.permit(:count, tag_ids: []) + end - # destroy all notifications related to this course - def destroy_notifications - Notification.where(notifiable_id: @course.id, notifiable_type: 'Course') - .delete_all - end + def search_params + params.require(:search).permit(:all_editors, :all_programs, :fulltext, + :term_independent, :per, + editor_ids: [], + program_ids: []) + end - # fill organizational_concept with default view - def set_organizational_defaults - @course.update(organizational_concept: - render_to_string(partial: 'courses/' \ - 'organizational_default', - formats: :html, - layout: false)) - end + def random_quiz_params + params.require(:quiz).permit(:random_quiz_count, + search_course_tag_ids: []) + end - def check_if_enough_questions - return if @course.enough_questions? - redirect_to :root, alert: I18n.t('controllers.no_test') - end + # destroy all notifications related to this course + def destroy_notifications + Notification.where(notifiable_id: @course.id, notifiable_type: 'Course') + .delete_all + end - def check_for_consent - redirect_to consent_profile_path unless current_user.consents - end + # fill organizational_concept with default view + def set_organizational_defaults + @course.update(organizational_concept: + render_to_string(partial: 'courses/' \ + 'organizational_default', + formats: :html, + layout: false)) + end + + def check_if_enough_questions + return if @course.enough_questions? + + redirect_to :root, alert: I18n.t('controllers.no_test') + end + + def check_for_consent + redirect_to consent_profile_path unless current_user.consents + end end diff --git a/app/controllers/divisions_controller.rb b/app/controllers/divisions_controller.rb index c683c4d0f..9c8ecdd53 100644 --- a/app/controllers/divisions_controller.rb +++ b/app/controllers/divisions_controller.rb @@ -7,42 +7,42 @@ def current_ability @current_ability ||= DivisionAbility.new(current_user) end - def edit - end + def edit + end - def new - @division = Division.new(program_id: params[:program_id].to_i) + def new + @division = Division.new(program_id: params[:program_id].to_i) authorize! :new, @division - end + end - def update - @division.update(division_params) - redirect_to classification_path - end + def update + @division.update(division_params) + redirect_to classification_path + end - def create - @division = Division.new(division_params) - @division.program_id = params[:division][:program_id] + def create + @division = Division.new(division_params) + @division.program_id = params[:division][:program_id] authorize! :create, @division - @division.save - redirect_to classification_path - end + @division.save + redirect_to classification_path + end - def destroy - @division.destroy - redirect_to classification_path - end + def destroy + @division.destroy + redirect_to classification_path + end private - def set_division - @division = Division.find_by_id(params[:id]) - return if @division.present? + def set_division + @division = Division.find_by_id(params[:id]) + return if @division.present? - redirect_to root_path, alert: I18n.t('controllers.no_division') - end + redirect_to root_path, alert: I18n.t('controllers.no_division') + end - def division_params - params.require(:division).permit(*Division.globalize_attribute_names) - end -end \ No newline at end of file + def division_params + params.require(:division).permit(*Division.globalize_attribute_names) + end +end diff --git a/app/controllers/erdbeere_controller.rb b/app/controllers/erdbeere_controller.rb index 22081e47c..d880b9114 100644 --- a/app/controllers/erdbeere_controller.rb +++ b/app/controllers/erdbeere_controller.rb @@ -1,46 +1,46 @@ # ExamplesController class ErdbeereController < ApplicationController authorize_resource class: false - layout 'application' + layout 'application' def current_ability @current_ability ||= ErdbeereAbility.new(current_user) end - def show_example + def show_example response = Faraday.get(ENV['ERDBEERE_API'] + "/examples/#{params[:id]}") @content = if response.status == 200 - JSON.parse(response.body)['embedded_html'] - else - 'Something went wrong.' - end - end + JSON.parse(response.body)['embedded_html'] + else + 'Something went wrong.' + end + end - def show_property + def show_property response = Faraday.get(ENV['ERDBEERE_API'] + "/properties/#{params[:id]}") - @content = if response.status == 200 - JSON.parse(response.body)['embedded_html'] - else - 'Something went wrong.' - end - end + @content = if response.status == 200 + JSON.parse(response.body)['embedded_html'] + else + 'Something went wrong.' + end + end - def show_structure - id = params[:id] + def show_structure + id = params[:id] response = Faraday.get(ENV['ERDBEERE_API'] + "/structures/#{params[:id]}") @content = if response.status == 200 - JSON.parse(response.body)['embedded_html'] - else - 'Something went wrong.' - end - end + JSON.parse(response.body)['embedded_html'] + else + 'Something went wrong.' + end + end - def find_tags - @sort = params[:sort] - @id = params[:id].to_i - @tags = Tag.find_erdbeere_tags(@sort, @id) - end + def find_tags + @sort = params[:sort] + @id = params[:id].to_i + @tags = Tag.find_erdbeere_tags(@sort, @id) + end def edit_tags end @@ -59,11 +59,11 @@ def display_info return end @info = if @sort == 'Structure' - @content['data']['attributes']['name'] - else - "#{@content['included'][0]['attributes']['name']}:"\ - "#{@content['data']['attributes']['name']}" - end + @content['data']['attributes']['name'] + else + "#{@content['included'][0]['attributes']['name']}:"\ + "#{@content['data']['attributes']['name']}" + end end def update_tags @@ -93,7 +93,9 @@ def fill_realizations_select @structures = hash['data'].map do |d| { id: d['id'], name: d['attributes']['name'], - properties: d['relationships']['original_properties']['data'].map { |x| x['id'] }} + properties: d['relationships']['original_properties']['data'].map { |x| + x['id'] + } } end @properties = hash['included'].map do |d| { id: d['id'], @@ -104,21 +106,21 @@ def fill_realizations_select def find_example response = Faraday.get(ENV['ERDBEERE_API'] + '/find?' + find_params.to_query) @content = if response.status == 200 - JSON.parse(response.body)['embedded_html'] - else - 'Something went wrong.' - end + JSON.parse(response.body)['embedded_html'] + else + 'Something went wrong.' + end end private - def erdbeere_params - params.require(:erdbeere).permit(:sort, :id, tag_ids: []) - end + def erdbeere_params + params.require(:erdbeere).permit(:sort, :id, tag_ids: []) + end - def find_params - params.require(:find).permit(:structure_id, - satisfies: [], - violates: []) - end -end \ No newline at end of file + def find_params + params.require(:find).permit(:structure_id, + satisfies: [], + violates: []) + end +end diff --git a/app/controllers/interactions_controller.rb b/app/controllers/interactions_controller.rb index 5892ed34b..81242996b 100644 --- a/app/controllers/interactions_controller.rb +++ b/app/controllers/interactions_controller.rb @@ -7,30 +7,34 @@ def index end def export_interactions - start_date = interaction_params[:start_date].to_date - end_date = interaction_params[:end_date].to_date - @interactions = Interaction.created_between(start_date, end_date) + start_date = interaction_params[:start_date].to_date + end_date = interaction_params[:end_date].to_date + @interactions = Interaction.created_between(start_date, end_date) respond_to do |format| format.html { head :ok } - format.csv { send_data @interactions.to_csv, - filename: "interactions-from-#{start_date}-to-#{end_date}-at-#{Time.now}.csv" } + format.csv { + send_data @interactions.to_csv, + filename: "interactions-from-#{start_date}-to-#{end_date}-at-#{Time.now}.csv" + } end end def export_probes - start_date = interaction_params[:start_date].to_date - end_date = interaction_params[:end_date].to_date - @probes = Probe.created_between(start_date, end_date) + start_date = interaction_params[:start_date].to_date + end_date = interaction_params[:end_date].to_date + @probes = Probe.created_between(start_date, end_date) respond_to do |format| format.html { head :ok } - format.csv { send_data @probes.to_csv, - filename: "probes-from-#{start_date}-to-#{end_date}-at-#{Time.now}.csv" } + format.csv { + send_data @probes.to_csv, + filename: "probes-from-#{start_date}-to-#{end_date}-at-#{Time.now}.csv" + } end end private - def interaction_params - params.require(:interactions).permit(:start_date, :end_date) - end -end \ No newline at end of file + def interaction_params + params.require(:interactions).permit(:start_date, :end_date) + end +end diff --git a/app/controllers/items_controller.rb b/app/controllers/items_controller.rb index 67ec44559..56c0bf885 100644 --- a/app/controllers/items_controller.rb +++ b/app/controllers/items_controller.rb @@ -50,27 +50,28 @@ def display private - def set_item - @item = Item.find(params[:id]) - end + def set_item + @item = Item.find(params[:id]) + end + + def set_explanation + if @referral_id.zero? || @item != Referral.find(@referral_id).item + return @item.explanation + end - def set_explanation - if @referral_id.zero? || @item != Referral.find(@referral_id).item - return @item.explanation + Referral.find(@referral_id).explanation end - Referral.find(@referral_id).explanation - end - def item_params - # params are cloned and then start time is converted to a TimeStamp object - filter = params.require(:item).permit(:sort, :start_time, :section_id, - :medium_id, :ref_number, :description, - :link, :page, :pdf_destination, - :explanation, :hidden).clone - if filter[:medium_id].present? - filter[:start_time] = TimeStamp.new(time_string: filter[:start_time]) + def item_params + # params are cloned and then start time is converted to a TimeStamp object + filter = params.require(:item).permit(:sort, :start_time, :section_id, + :medium_id, :ref_number, :description, + :link, :page, :pdf_destination, + :explanation, :hidden).clone + if filter[:medium_id].present? + filter[:start_time] = TimeStamp.new(time_string: filter[:start_time]) + end + filter[:section_id] = nil if filter[:section_id] == '' + filter end - filter[:section_id] = nil if filter[:section_id] == '' - filter - end end diff --git a/app/controllers/lectures_controller.rb b/app/controllers/lectures_controller.rb index bf7a63ffb..bf1aab76c 100644 --- a/app/controllers/lectures_controller.rb +++ b/app/controllers/lectures_controller.rb @@ -44,11 +44,11 @@ def update .new_editor_email.deliver_later end end - + @lecture.update(lecture_params) if structure_params.present? structure_ids = structure_params.select { |_k, v| v.to_i == 1 }.keys - .map(&:to_i) + .map(&:to_i) @lecture.update(structure_ids: structure_ids) end @lecture.touch @@ -74,7 +74,8 @@ def show chapters: [:lecture, sections: [lessons: [:tags], chapter: [:lecture], - tags: [:notions, :lessons]]]) + tags: [:notions, + :lessons]]]) .find_by_id(params[:id]) @notifications = current_user.active_notifications(@lecture) @new_topics_count = @lecture.unread_forum_topics_count(current_user) || 0 @@ -87,6 +88,7 @@ def new authorize! :new, @lecture @from = params[:from] return unless @from == 'course' + # if new action was triggered from inside a course view, add the course # info to the lecture @lecture.course = Course.find_by_id(params[:course]) @@ -113,7 +115,7 @@ def create end redirect_to edit_course_path(@lecture.course), notice: I18n.t('controllers.created_lecture_success', - lecture: @lecture.title_with_teacher) + lecture: @lecture.title_with_teacher) return end @errors = @lecture.errors @@ -247,6 +249,7 @@ def search @results_as_list = search_params[:results_as_list] == 'true' return unless @total.zero? return unless search_params[:fulltext]&.length.to_i > 1 + @similar_titles = Course.similar_courses(search_params[:fulltext]) end @@ -267,7 +270,7 @@ def subscribe_page def import_toc imported_lecture = Lecture - .find_by_id(import_toc_params[:imported_lecture_id]) + .find_by_id(import_toc_params[:imported_lecture_id]) import_sections = import_toc_params[:import_sections] == '1' import_tags = import_toc_params[:import_tags] == '1' @lecture.import_toc!(imported_lecture, import_sections, import_tags) @@ -276,154 +279,157 @@ def import_toc private - def set_lecture - @lecture = Lecture.find_by_id(params[:id]) - return if @lecture - redirect_to :root, alert: I18n.t('controllers.no_lecture') - end + def set_lecture + @lecture = Lecture.find_by_id(params[:id]) + return if @lecture - def set_lecture_cookie - cookies[:current_lecture_id] = @lecture.id - end + redirect_to :root, alert: I18n.t('controllers.no_lecture') + end - def set_view_locale - I18n.locale = @lecture.locale_with_inheritance || current_user.locale || + def set_lecture_cookie + cookies[:current_lecture_id] = @lecture.id + end + + def set_view_locale + I18n.locale = @lecture.locale_with_inheritance || current_user.locale || I18n.default_locale - end + end - def check_for_consent - redirect_to consent_profile_path unless current_user.consents - end + def check_for_consent + redirect_to consent_profile_path unless current_user.consents + end - def check_for_subscribe - redirect_to subscribe_lecture_page_path(@lecture.id) unless @lecture.in?(current_user.lectures) - end + def check_for_subscribe + redirect_to subscribe_lecture_page_path(@lecture.id) unless @lecture.in?(current_user.lectures) + end - def lecture_params - params.require(:lecture).permit(:course_id, :term_id, :teacher_id, - :start_chapter, :absolute_numbering, - :start_section, :organizational, :locale, - :organizational_concept, :muesli, - :organizational_on_top, - :disable_teacher_display, - :content_mode, :passphrase, :sort, - :comments_disabled, - :submission_max_team_size, - :submission_grace_period, - editor_ids: []) - end + def lecture_params + params.require(:lecture).permit(:course_id, :term_id, :teacher_id, + :start_chapter, :absolute_numbering, + :start_section, :organizational, :locale, + :organizational_concept, :muesli, + :organizational_on_top, + :disable_teacher_display, + :content_mode, :passphrase, :sort, + :comments_disabled, + :submission_max_team_size, + :submission_grace_period, + editor_ids: []) + end - def structure_params - params.require(:lecture).permit(structures: {})[:structures] - end + def structure_params + params.require(:lecture).permit(structures: {})[:structures] + end - def comment_params - params.require(:lecture).permit(:close_comments) - end + def comment_params + params.require(:lecture).permit(:close_comments) + end - def import_toc_params - params.permit(:imported_lecture_id, :import_sections, :import_tags) - end + def import_toc_params + params.permit(:imported_lecture_id, :import_sections, :import_tags) + end - # create notifications to all users about creation of new lecture - def create_notifications - notifications = [] - User.find_each do |u| - notifications << Notification.new(recipient: u, - notifiable_id: @lecture.id, - notifiable_type: 'Lecture', - action: 'create') + # create notifications to all users about creation of new lecture + def create_notifications + notifications = [] + User.find_each do |u| + notifications << Notification.new(recipient: u, + notifiable_id: @lecture.id, + notifiable_type: 'Lecture', + action: 'create') + end + Notification.import notifications end - Notification.import notifications - end - def send_notification_email - recipients = User.where(email_for_teachable: true) - I18n.available_locales.each do |l| - local_recipients = recipients.where(locale: l) - if local_recipients.any? - NotificationMailer.with(recipients: local_recipients.pluck(:id), - locale: l, - lecture: @lecture) - .new_lecture_email.deliver_later + def send_notification_email + recipients = User.where(email_for_teachable: true) + I18n.available_locales.each do |l| + local_recipients = recipients.where(locale: l) + if local_recipients.any? + NotificationMailer.with(recipients: local_recipients.pluck(:id), + locale: l, + lecture: @lecture) + .new_lecture_email.deliver_later + end end end - end - # destroy all notifications related to this lecture - def destroy_notifications - Notification.where(notifiable_id: @lecture.id, notifiable_type: 'Lecture') - .delete_all - end + # destroy all notifications related to this lecture + def destroy_notifications + Notification.where(notifiable_id: @lecture.id, notifiable_type: 'Lecture') + .delete_all + end - # fill organizational_concept with default view - def set_organizational_defaults - partial_path = 'lectures/organizational/' - partial_path += @lecture.seminar? ? 'seminar' : 'lecture' - @lecture.update(organizational_concept: - render_to_string(partial: partial_path, - formats: :html, - layout: false)) - end + # fill organizational_concept with default view + def set_organizational_defaults + partial_path = 'lectures/organizational/' + partial_path += @lecture.seminar? ? 'seminar' : 'lecture' + @lecture.update(organizational_concept: + render_to_string(partial: partial_path, + formats: :html, + layout: false)) + end - # set language to default language - def set_language - @lecture.update(locale: I18n.default_locale.to_s) - end + # set language to default language + def set_language + @lecture.update(locale: I18n.default_locale.to_s) + end - def eager_load_stuff - @lecture = Lecture.includes(:teacher, :term, :editors, - :announcements, :imported_media, - course: [:editors], - media: [:teachable, :tags], - lessons: [media: [:tags]], - chapters: [:lecture, - sections: [lessons: [:tags], - chapter: [:lecture], - tags: [:notions, :lessons]]]) - .find_by_id(params[:id]) - @media = @lecture.media_with_inheritance_uncached_eagerload_stuff - lecture_tags = @lecture.tags - @course_tags = @lecture.course_tags(lecture_tags: lecture_tags) - @extra_tags = @lecture.extra_tags(lecture_tags: lecture_tags) - @deferred_tags = @lecture.deferred_tags(lecture_tags: lecture_tags) - @announcements = @lecture.announcements.includes(:announcer).order(:created_at).reverse - @terms = Term.select_terms - end + def eager_load_stuff + @lecture = Lecture.includes(:teacher, :term, :editors, + :announcements, :imported_media, + course: [:editors], + media: [:teachable, :tags], + lessons: [media: [:tags]], + chapters: [:lecture, + sections: [lessons: [:tags], + chapter: [:lecture], + tags: [:notions, + :lessons]]]) + .find_by_id(params[:id]) + @media = @lecture.media_with_inheritance_uncached_eagerload_stuff + lecture_tags = @lecture.tags + @course_tags = @lecture.course_tags(lecture_tags: lecture_tags) + @extra_tags = @lecture.extra_tags(lecture_tags: lecture_tags) + @deferred_tags = @lecture.deferred_tags(lecture_tags: lecture_tags) + @announcements = @lecture.announcements.includes(:announcer).order(:created_at).reverse + @terms = Term.select_terms + end - def set_erdbeere_data - @structure_ids = @lecture.structure_ids - response = Faraday.get(ENV['ERDBEERE_API'] + '/structures') - response_hash = if response.status == 200 - JSON.parse(response.body) - else - { 'data' => {}, 'included' => {} } - end - @all_structures = response_hash['data'] - @structures = @all_structures.select do |s| - s['id'].to_i.in?(@structure_ids) + def set_erdbeere_data + @structure_ids = @lecture.structure_ids + response = Faraday.get(ENV['ERDBEERE_API'] + '/structures') + response_hash = if response.status == 200 + JSON.parse(response.body) + else + { 'data' => {}, 'included' => {} } + end + @all_structures = response_hash['data'] + @structures = @all_structures.select do |s| + s['id'].to_i.in?(@structure_ids) + end + @properties = response_hash['included'] end - @properties = response_hash['included'] - end - def search_params - types = params[:search][:types] - types = [types] if types && !types.kind_of?(Array) - types -= [''] if types - types = nil if types == [] - params[:search][:types] = types - params[:search][:user_id] = current_user.id - params.require(:search).permit(:all_types, :all_terms, :all_programs, - :all_teachers, :fulltext, :per, :user_id, - :results_as_list, - types: [], - term_ids: [], - program_ids: [], - teacher_ids: []) - end + def search_params + types = params[:search][:types] + types = [types] if types && !types.kind_of?(Array) + types -= [''] if types + types = nil if types == [] + params[:search][:types] = types + params[:search][:user_id] = current_user.id + params.require(:search).permit(:all_types, :all_terms, :all_programs, + :all_teachers, :fulltext, :per, :user_id, + :results_as_list, + types: [], + term_ids: [], + program_ids: [], + teacher_ids: []) + end - def check_if_enough_questions - return if @lecture.course.enough_questions? - redirect_to :root, alert: I18n.t('controllers.no_test') - end + def check_if_enough_questions + return if @lecture.course.enough_questions? + + redirect_to :root, alert: I18n.t('controllers.no_test') + end end diff --git a/app/controllers/lessons_controller.rb b/app/controllers/lessons_controller.rb index 546c8a9e9..2425dce7d 100644 --- a/app/controllers/lessons_controller.rb +++ b/app/controllers/lessons_controller.rb @@ -46,9 +46,11 @@ def update @lesson.update(lesson_params) @errors = @lesson.errors return unless @errors.blank? + update_media_order if params[:lesson][:media_order] @tags_without_section = @lesson.tags_without_section return unless @lesson.sections.count == 1 && @tags_without_section.any? + section = @lesson.sections.first section.tags << @tags_without_section end @@ -69,28 +71,31 @@ def destroy private - def set_lesson - @lesson = Lesson.find_by_id(params[:id]) - return if @lesson.present? - redirect_to :root, alert: I18n.t('controllers.no_lesson') - end + def set_lesson + @lesson = Lesson.find_by_id(params[:id]) + return if @lesson.present? - def lesson_params - params.require(:lesson).permit(:date, :lecture_id, :start_destination, - :end_destination, :details, - section_ids: [], - tag_ids: []) - end + redirect_to :root, alert: I18n.t('controllers.no_lesson') + end - def update_media_order - media_order_from_json = JSON.parse(params[:lesson][:media_order]) - return unless media_order_from_json.is_a?(Array) - media_order = media_order_from_json.map(&:to_i) - [0] - return unless media_order.sort == @lesson.media.pluck(:id).sort - Medium.acts_as_list_no_update do - @lesson.media.each do |m| - m.update(position: media_order.index(m.id)) + def lesson_params + params.require(:lesson).permit(:date, :lecture_id, :start_destination, + :end_destination, :details, + section_ids: [], + tag_ids: []) + end + + def update_media_order + media_order_from_json = JSON.parse(params[:lesson][:media_order]) + return unless media_order_from_json.is_a?(Array) + + media_order = media_order_from_json.map(&:to_i) - [0] + return unless media_order.sort == @lesson.media.pluck(:id).sort + + Medium.acts_as_list_no_update do + @lesson.media.each do |m| + m.update(position: media_order.index(m.id)) + end end end - end end diff --git a/app/controllers/main_controller.rb b/app/controllers/main_controller.rb index d1769a925..2d8653eae 100644 --- a/app/controllers/main_controller.rb +++ b/app/controllers/main_controller.rb @@ -1,6 +1,5 @@ # MainController class MainController < ApplicationController - before_action :check_for_consent authorize_resource class: false, only: :start layout 'application_no_sidebar' @@ -34,7 +33,7 @@ def comments @media_comments.select! do |m| (Reader.find_by(user: current_user, thread: m[:thread]) &.updated_at || (Time.now - 1000.years)) < m[:latest_comment].created_at && - m[:medium].visible_for_user?(current_user) + m[:medium].visible_for_user?(current_user) end @media_array = Kaminari.paginate_array(@media_comments) .page(params[:page]).per(10) @@ -52,15 +51,16 @@ def start @talks = current_user.talks.includes(lecture: :term) .select { |t| t.visible_for_user?(current_user) } .sort_by do |t| - [-t.lecture.term.begin_date.jd, - t.position] + [-t.lecture.term.begin_date.jd, + t.position] end end private - def check_for_consent - return unless user_signed_in? - redirect_to consent_profile_path unless current_user.consents - end + def check_for_consent + return unless user_signed_in? + + redirect_to consent_profile_path unless current_user.consents + end end diff --git a/app/controllers/media_controller.rb b/app/controllers/media_controller.rb index a9082d3e6..f3e5678c5 100644 --- a/app/controllers/media_controller.rb +++ b/app/controllers/media_controller.rb @@ -65,6 +65,7 @@ def update @medium.update(medium_params) @errors = @medium.errors return unless @errors.empty? + # make sure the medium is touched # (it will not be touched automatically in some cases (e.g. if you only # update the associated tags), causing trouble for caching) @@ -93,13 +94,13 @@ def update # refreshed_video = @medium.video # @medium.update(video_data: refreshed_video.to_json) end - if @medium.sort == 'Quiz' &¶ms[:medium][:create_quiz_graph] == '1' + if @medium.sort == 'Quiz' && params[:medium][:create_quiz_graph] == '1' @medium.becomes(Quiz).update(level: 1, quiz_graph: QuizGraph.new(vertices: {}, - edges: {}, - root: 0, - default_table: {}, - hide_solution: [])) + edges: {}, + root: 0, + default_table: {}, + hide_solution: [])) end # if changes to the manuscript have been made, # remove items that correspond to named destinations that no longer @@ -123,6 +124,7 @@ def update end @tags_without_section = [] return unless @medium.teachable.class.to_s == 'Lesson' + add_tags_in_lesson_and_sections end @@ -154,11 +156,11 @@ def create end if @medium.sort == 'Quiz' @medium.update(type: 'Quiz') - @medium.update(quiz_graph:QuizGraph.new(vertices: {}, - edges: {}, - root: 0, - default_table: {}, - hide_solution: []), + @medium.update(quiz_graph: QuizGraph.new(vertices: {}, + edges: {}, + root: 0, + default_table: {}, + hide_solution: []), level: 1) end redirect_to edit_medium_path(@medium) @@ -172,6 +174,7 @@ def publish publisher = MediumPublisher.parse(@medium, current_user, publish_params) @errors = publisher.errors return if @errors.present? + @medium.update(publisher: publisher) if publisher.release_now @medium.publish! @@ -217,14 +220,15 @@ def search filter_media = true params["search"]["access"] = 'irrelevant' end - params["search"]["answers_count"] = 'irrelevant' if search_params[:answers_count].blank? + params["search"]["answers_count"] = + 'irrelevant' if search_params[:answers_count].blank? search = Medium.search_by(search_params, params[:page]) search.execute results = search.results @total = search.total - # in the case of a search with tag_operator 'or', we + # in the case of a search with tag_operator 'or', we # execute two searches and merge the results, where media # with the selected tags are now shown at the front of the list if search_params[:tag_operator] == "or" and search_params[:all_tags] == "0" and search_params[:fulltext].size >= 2 @@ -304,7 +308,7 @@ def add_item @item = Item.new(medium: @medium, start_time: TimeStamp.new(total_seconds: @time)) if @medium.sort == 'Kaviar' && - @medium.teachable_type.in?(['Lesson', 'Lecture']) + @medium.teachable_type.in?(['Lesson', 'Lecture']) @item.section = @medium.teachable&.sections&.first end end @@ -341,6 +345,7 @@ def add_screenshot # remove the video's screenshot def remove_screenshot return if @medium.screenshot.nil? + @medium.update(screenshot: nil) end @@ -376,7 +381,7 @@ def fill_teachable_select result = (Course.editable_selection(current_user) + Lecture.editable_selection(current_user) + Lesson.editable_selection(current_user)) - .map { |t| { value: t[1], text: t[0] } } + .map { |t| { value: t[1], text: t[0] } } render json: result end @@ -402,22 +407,36 @@ def get_statistics medium_consumption = Consumption.where(medium_id: @medium.id) if @medium.video.present? @video_downloads = medium_consumption.where(sort: 'video', - mode: 'download').pluck(:created_at).map(&:to_date).tally.map{|k,t| {x: k,y:t}}.to_json + mode: 'download').pluck(:created_at).map(&:to_date).tally.map { |k, t| + { + x: k, y: t + } + }.to_json @video_downloads_count = medium_consumption.where(sort: 'video', - mode: 'download').count + mode: 'download').count @video_thyme = medium_consumption.where(sort: 'video', - mode: 'thyme').pluck(:created_at).map(&:to_date).tally.map{|k,t| {x: k,y:t}}.to_json + mode: 'thyme').pluck(:created_at).map(&:to_date).tally.map { |k, t| + { + x: k, y: t + } + }.to_json @video_thyme_count = medium_consumption.where(sort: 'video', mode: 'thyme').count end if @medium.manuscript.present? - @manuscript_access = medium_consumption.where(sort: 'manuscript').pluck(:created_at).map(&:to_date).tally.map{|k,t| {x: k,y:t}}.to_json + @manuscript_access = medium_consumption.where(sort: 'manuscript').pluck(:created_at).map(&:to_date).tally.map { |k, t| + { x: k, y: t } + }.to_json @manuscript_access_count = medium_consumption.where(sort: 'manuscript').count end if @medium.sort == 'Quiz' - @quiz_plays = medium_consumption.where(sort: 'quiz', mode: 'browser').pluck(:created_at).map(&:to_date).tally.map{|k,t| {x: k,y:t}}.to_json - @quiz_plays_count = medium_consumption.where(sort: 'quiz', mode: 'browser').count + @quiz_plays = medium_consumption.where(sort: 'quiz', + mode: 'browser').pluck(:created_at).map(&:to_date).tally.map { |k, t| + { x: k, y: t } + }.to_json + @quiz_plays_count = medium_consumption.where(sort: 'quiz', + mode: 'browser').count @quiz_finished_count = Probe.finished_quizzes(@medium) @global_success = Probe.global_success_in_quiz(@medium.becomes(Quiz)) @global_success_details = Probe.global_success_details(@medium.becomes(Quiz)) @@ -498,201 +517,205 @@ def fill_reassign_modal private - def medium_params - params.require(:medium).permit(:sort, :description, :video, :manuscript, - :external_reference_link, - :external_link_description, - :geogebra, :geogebra_app_name, - :teachable_type, :teachable_id, - :released, :text, :locale, - :content, :boost, - editor_ids: [], - tag_ids: [], - linked_medium_ids: []) - end - - def publish_params - params.require(:medium).permit(:release_now, :released, :release_date, - :lock_comments, :publish_vertices, - :create_assignment, :assignment_title, - :assignment_deadline, :assignment_file_type, - :assignment_deletion_date) - end - - def set_medium - @medium = Medium.find_by_id(params[:id])&.becomes(Medium) - return if @medium.present? && @medium.sort != 'RandomQuiz' - redirect_to :root, alert: I18n.t('controllers.no_medium') - end - - def set_lecture - @lecture = Lecture.find_by_id(params[:id]) - # store current lecture in cookie - if @lecture - cookies[:current_lecture_id] = @lecture.id - return + def medium_params + params.require(:medium).permit(:sort, :description, :video, :manuscript, + :external_reference_link, + :external_link_description, + :geogebra, :geogebra_app_name, + :teachable_type, :teachable_id, + :released, :text, :locale, + :content, :boost, + editor_ids: [], + tag_ids: [], + linked_medium_ids: []) + end + + def publish_params + params.require(:medium).permit(:release_now, :released, :release_date, + :lock_comments, :publish_vertices, + :create_assignment, :assignment_title, + :assignment_deadline, :assignment_file_type, + :assignment_deletion_date) + end + + def set_medium + @medium = Medium.find_by_id(params[:id])&.becomes(Medium) + return if @medium.present? && @medium.sort != 'RandomQuiz' + + redirect_to :root, alert: I18n.t('controllers.no_medium') + end + + def set_lecture + @lecture = Lecture.find_by_id(params[:id]) + # store current lecture in cookie + if @lecture + cookies[:current_lecture_id] = @lecture.id + return + end + redirect_to :root, alert: I18n.t('controllers.no_lecture') + end + + def set_teachable + if params[:teachable_type].in?(['Course', 'Lecture', 'Lesson', 'Talk']) && + params[:teachable_id].present? + @teachable = params[:teachable_type].constantize + .find_by_id(params[:teachable_id]) + end + end + + def detach_components + if params[:medium][:detach_video] == 'true' + @medium.update(video: nil) + @medium.update(screenshot: nil) + end + if params[:medium][:detach_geogebra] == 'true' || @medium.sort != 'Sesam' + @medium.update(geogebra: nil) + end + return unless params[:medium][:detach_manuscript] == 'true' + + @medium.update(manuscript: nil) + end + + def sanitize_params + reveal_contradictions + sanitize_page! + sanitize_per! + params[:all] = (params[:all] == 'true') || (cookies[:all] == 'true') + cookies[:all] = params[:all] + cookies[:per] = false if cookies[:all] + params[:reverse] = params[:reverse] == 'true' + end + + def check_for_consent + redirect_to consent_profile_path unless current_user.consents end - redirect_to :root, alert: I18n.t('controllers.no_lecture') - end - - def set_teachable - if params[:teachable_type].in?(['Course', 'Lecture', 'Lesson', 'Talk']) && - params[:teachable_id].present? - @teachable = params[:teachable_type].constantize - .find_by_id(params[:teachable_id]) - end - end - - def detach_components - if params[:medium][:detach_video] == 'true' - @medium.update(video: nil) - @medium.update(screenshot: nil) - end - if params[:medium][:detach_geogebra] == 'true' || @medium.sort != 'Sesam' - @medium.update(geogebra: nil) - end - return unless params[:medium][:detach_manuscript] == 'true' - @medium.update(manuscript: nil) - end - - def sanitize_params - reveal_contradictions - sanitize_page! - sanitize_per! - params[:all] = (params[:all] == 'true') || (cookies[:all] == 'true') - cookies[:all] = params[:all] - cookies[:per] = false if cookies[:all] - params[:reverse] = params[:reverse] == 'true' - end - - def check_for_consent - redirect_to consent_profile_path unless current_user.consents - end - - # paginate results obtained by the search_results method - def paginated_results - if params[:all] - total_count = search_results.count - # without the total count parameter, kaminary will consider only only the - # first 25 entries - return Kaminari.paginate_array(search_results, - total_count: total_count + 1) - end - Kaminari.paginate_array(search_results).page(params[:page]) - .per(params[:per]) - end - - # search is done in search class method for Medium - def search_results - search_results = Medium.search_all(params) - # search_results are ordered in a certain way - # the next lines ensure that filtering for visible media does not - # mess up the ordering - search_arel = Medium.where(id: search_results.pluck(:id)) - visible_search_results = current_user.filter_visible_media(search_arel) - search_results &= visible_search_results - total = search_results.size - @lecture = Lecture.find_by_id(params[:id]) - # filter out stuff from course level for generic users - if params[:visibility] == 'lecture' - search_results.reject! { |m| m.teachable_type == 'Course' } - # yields only lecture media and course media - elsif params[:visibility] == 'all' - # yields all lecture media and course media - else - # this is the default setting: 'thematic' selection of media - # yields all lecture media and course media whose tags have - # already been dealt with in the lecture - unless current_user.admin || @lecture.edited_by?(current_user) - lecture_tags = @lecture.tags_including_media_tags - search_results.reject! do |m| - m.teachable_type == 'Course' && (m.tags & lecture_tags).blank? + + # paginate results obtained by the search_results method + def paginated_results + if params[:all] + total_count = search_results.count + # without the total count parameter, kaminary will consider only only the + # first 25 entries + return Kaminari.paginate_array(search_results, + total_count: total_count + 1) + end + Kaminari.paginate_array(search_results).page(params[:page]) + .per(params[:per]) + end + + # search is done in search class method for Medium + def search_results + search_results = Medium.search_all(params) + # search_results are ordered in a certain way + # the next lines ensure that filtering for visible media does not + # mess up the ordering + search_arel = Medium.where(id: search_results.pluck(:id)) + visible_search_results = current_user.filter_visible_media(search_arel) + search_results &= visible_search_results + total = search_results.size + @lecture = Lecture.find_by_id(params[:id]) + # filter out stuff from course level for generic users + if params[:visibility] == 'lecture' + search_results.reject! { |m| m.teachable_type == 'Course' } + # yields only lecture media and course media + elsif params[:visibility] == 'all' + # yields all lecture media and course media + else + # this is the default setting: 'thematic' selection of media + # yields all lecture media and course media whose tags have + # already been dealt with in the lecture + unless current_user.admin || @lecture.edited_by?(current_user) + lecture_tags = @lecture.tags_including_media_tags + search_results.reject! do |m| + m.teachable_type == 'Course' && (m.tags & lecture_tags).blank? + end end end + sort = params[:project] == 'keks' ? 'Quiz' : params[:project]&.capitalize + search_results += @lecture.imported_media + .where(sort: sort) + .locally_visible + search_results.uniq! + @hidden = search_results.empty? && total.positive? + return search_results unless params[:reverse] + + search_results.reverse end - sort = params[:project] == 'keks' ? 'Quiz' : params[:project]&.capitalize - search_results += @lecture.imported_media - .where(sort: sort) - .locally_visible - search_results.uniq! - @hidden = search_results.empty? && total.positive? - return search_results unless params[:reverse] - search_results.reverse - end - - def reveal_contradictions - return unless params[:lecture_id].present? - return if params[:lecture_id].to_i.in?(@course.lecture_ids) - redirect_to :root, alert: I18n.t('controllers.contradiction') - end - - def sanitize_page! - params[:page] = params[:page].to_i.positive? ? params[:page].to_i : 1 - end - - def sanitize_per! - if params[:per] || cookies[:per].to_i.positive? - cookies[:all] = 'false' - end - params[:per] = if params[:per].to_i.in?([3, 4, 8, 12, 24, 48]) - params[:per].to_i - elsif cookies[:per].to_i.positive? - cookies[:per].to_i - else - 8 - end - cookies[:per] = params[:per] - end - - def search_params - types = params[:search][:types] || [] - types = [types] if types && !types.kind_of?(Array) - types -= [''] if types - types = nil if types == [] - params[:search][:types] = types - params[:search][:user_id] = current_user.id - params.require(:search) - .permit(:all_types, :all_teachables, :all_tags, - :all_editors, :tag_operator, :quiz, :access, - :teachable_inheritance, :fulltext, :per, - :clicker, :purpose, :answers_count, - :results_as_list, :all_terms, :all_teachers, - :lecture_option, :user_id, - types: [], - teachable_ids: [], - tag_ids: [], - editor_ids: [], - term_ids: [], - teacher_ids: [], - media_lectures: []) - #.with_defaults(access: 'all') - end - - # destroy all notifications related to this medium - def destroy_notifications - Notification.where(notifiable_id: @medium.id, notifiable_type: 'Medium') - .delete_all - end - - def add_tags_in_lesson_and_sections - @tags_outside_lesson = @medium.tags_outside_lesson - if @tags_outside_lesson - @medium.teachable.tags << @tags_outside_lesson - @tags_without_section = @tags_outside_lesson & @medium.teachable.tags_without_section - if @medium.teachable.sections.count == 1 - section = @medium.teachable.sections.first - section.tags << @tags_without_section + + def reveal_contradictions + return unless params[:lecture_id].present? + return if params[:lecture_id].to_i.in?(@course.lecture_ids) + + redirect_to :root, alert: I18n.t('controllers.contradiction') + end + + def sanitize_page! + params[:page] = params[:page].to_i.positive? ? params[:page].to_i : 1 + end + + def sanitize_per! + if params[:per] || cookies[:per].to_i.positive? + cookies[:all] = 'false' end + params[:per] = if params[:per].to_i.in?([3, 4, 8, 12, 24, 48]) + params[:per].to_i + elsif cookies[:per].to_i.positive? + cookies[:per].to_i + else + 8 + end + cookies[:per] = params[:per] + end + + def search_params + types = params[:search][:types] || [] + types = [types] if types && !types.kind_of?(Array) + types -= [''] if types + types = nil if types == [] + params[:search][:types] = types + params[:search][:user_id] = current_user.id + params.require(:search) + .permit(:all_types, :all_teachables, :all_tags, + :all_editors, :tag_operator, :quiz, :access, + :teachable_inheritance, :fulltext, :per, + :clicker, :purpose, :answers_count, + :results_as_list, :all_terms, :all_teachers, + :lecture_option, :user_id, + types: [], + teachable_ids: [], + tag_ids: [], + editor_ids: [], + term_ids: [], + teacher_ids: [], + media_lectures: []) + # .with_defaults(access: 'all') end - end - def store_access - mode = action_name == 'play' ? 'thyme' : 'pdf_view' - sort = action_name == 'play' ? 'video' : 'manuscript' - ConsumptionSaver.perform_async(@medium.id, mode, sort) - end + # destroy all notifications related to this medium + def destroy_notifications + Notification.where(notifiable_id: @medium.id, notifiable_type: 'Medium') + .delete_all + end + + def add_tags_in_lesson_and_sections + @tags_outside_lesson = @medium.tags_outside_lesson + if @tags_outside_lesson + @medium.teachable.tags << @tags_outside_lesson + @tags_without_section = @tags_outside_lesson & @medium.teachable.tags_without_section + if @medium.teachable.sections.count == 1 + section = @medium.teachable.sections.first + section.tags << @tags_without_section + end + end + end - def store_download - ConsumptionSaver.perform_async(@medium.id, 'download', params[:sort]) - end + def store_access + mode = action_name == 'play' ? 'thyme' : 'pdf_view' + sort = action_name == 'play' ? 'video' : 'manuscript' + ConsumptionSaver.perform_async(@medium.id, mode, sort) + end + + def store_download + ConsumptionSaver.perform_async(@medium.id, 'download', params[:sort]) + end end diff --git a/app/controllers/notifications_controller.rb b/app/controllers/notifications_controller.rb index b65fda5dc..a43809799 100644 --- a/app/controllers/notifications_controller.rb +++ b/app/controllers/notifications_controller.rb @@ -29,6 +29,7 @@ def destroy_all def destroy_lecture_notifications lecture = Lecture.find_by_id(params[:lecture_id]) return unless lecture.present? + Notification.delete(current_user.active_notifications(lecture).pluck(:id)) current_user.touch render :destroy_all @@ -44,9 +45,10 @@ def destroy_news_notifications private - def set_notification - @notification = Notification.find_by_id(params[:id]) - return if @notification.present? - redirect_to :root, alert: I18n.t('controllers.no_notification') - end + def set_notification + @notification = Notification.find_by_id(params[:id]) + return if @notification.present? + + redirect_to :root, alert: I18n.t('controllers.no_notification') + end end diff --git a/app/controllers/profile_controller.rb b/app/controllers/profile_controller.rb index 978a90447..d74eeb08a 100644 --- a/app/controllers/profile_controller.rb +++ b/app/controllers/profile_controller.rb @@ -24,6 +24,7 @@ def edit def update check_passphrases return if @errors.present? + if @user.update(lectures: @lectures, name: @name, name_in_tutorials: @name_in_tutorials, @@ -51,6 +52,7 @@ def check_for_consent return end return unless @user.consents + redirect_to edit_profile_path, notice: t('profile.please_update') end @@ -69,20 +71,21 @@ def toggle_thread_subscription else @thread.unsubscribe(@user) end - @result = !!@thread.subscription_for(@user) + @result = !!@thread.subscription_for(@user) end end def subscribe_lecture @success = false if !@lecture.published? && !current_user.admin && - !@lecture.edited_by?(current_user) + !@lecture.edited_by?(current_user) @unpublished = true return end return if @lecture.passphrase.present? && - !@lecture.in?(current_user.lectures) && - @lecture.passphrase != @passphrase + !@lecture.in?(current_user.lectures) && + @lecture.passphrase != @passphrase + @success = current_user.subscribe_lecture!(@lecture) redirect_to lecture_path(@lecture) if @parent == "redirect" @@ -91,14 +94,15 @@ def subscribe_lecture def unsubscribe_lecture @success = current_user.unsubscribe_lecture!(@lecture) @none_left = case @parent - when 'current_subscribed' then current_user.current_subscribed_lectures - .empty? - when 'inactive' then current_user.inactive_lectures.empty? + when 'current_subscribed' then current_user.current_subscribed_lectures + .empty? + when 'inactive' then current_user.inactive_lectures.empty? end end def star_lecture return unless @lecture&.in?(current_user.lectures) + if !@lecture.in?(current_user.favorite_lectures) current_user.favorite_lectures << @lecture end @@ -111,6 +115,7 @@ def star_lecture def unstar_lecture return unless @lecture + current_user.favorite_lectures.delete(@lecture) current_user.touch end @@ -118,12 +123,13 @@ def unstar_lecture def show_accordion @collapse_id = params[:id] redirect_to :root and return unless @collapse_id.present? + @lectures = case @collapse_id - when 'collapseCurrentStuff' then current_user.current_subscribed_lectures - when 'collapseInactiveLectures' then current_user.inactive_lectures - .includes(:course, :term) - .sort - when 'collapseAllCurrent' then current_user.current_subscribable_lectures + when 'collapseCurrentStuff' then current_user.current_subscribed_lectures + when 'collapseInactiveLectures' then current_user.inactive_lectures + .includes(:course, :term) + .sort + when 'collapseAllCurrent' then current_user.current_subscribable_lectures end @link = @collapse_id.remove('collapse').camelize(:lower) + 'Link' end @@ -136,81 +142,81 @@ def request_data private - def set_user - @user = current_user - end + def set_user + @user = current_user + end - def set_basics - @subscription_type = params[:user][:subscription_type].to_i - @name = params[:user][:name] - @name_in_tutorials = params[:user][:name_in_tutorials] - @lectures = Lecture.where(id: lecture_ids) - @courses = Course.where(id: @lectures.pluck(:course_id).uniq) - @locale = params[:user][:locale] - end + def set_basics + @subscription_type = params[:user][:subscription_type].to_i + @name = params[:user][:name] + @name_in_tutorials = params[:user][:name_in_tutorials] + @lectures = Lecture.where(id: lecture_ids) + @courses = Course.where(id: @lectures.pluck(:course_id).uniq) + @locale = params[:user][:locale] + end - def email_params - params.require(:user).permit(:email_for_medium, :email_for_announcement, - :email_for_teachable, :email_for_news, - :email_for_submission_upload, - :email_for_submission_removal, - :email_for_submission_join, - :email_for_submission_leave, - :email_for_correction_upload, - :email_for_submission_decision) - end + def email_params + params.require(:user).permit(:email_for_medium, :email_for_announcement, + :email_for_teachable, :email_for_news, + :email_for_submission_upload, + :email_for_submission_removal, + :email_for_submission_join, + :email_for_submission_leave, + :email_for_correction_upload, + :email_for_submission_decision) + end - def set_lecture - @lecture = Lecture.find_by_id(lecture_params[:id]) - @passphrase = lecture_params[:passphrase] - @parent = lecture_params[:parent] - @current = !@parent.in?(['lectureSearch', 'inactive']) - redirect_to start_path unless @lecture - end + def set_lecture + @lecture = Lecture.find_by_id(lecture_params[:id]) + @passphrase = lecture_params[:passphrase] + @parent = lecture_params[:parent] + @current = !@parent.in?(['lectureSearch', 'inactive']) + redirect_to start_path unless @lecture + end - def lecture_params - params.require(:lecture).permit(:id, :passphrase, :parent) - end + def lecture_params + params.require(:lecture).permit(:id, :passphrase, :parent) + end - # extracts all lecture ids from user params - def lecture_ids - params[:user][:lecture].select { |k, v| v == '1' }.keys.map(&:to_i) - end + # extracts all lecture ids from user params + def lecture_ids + params[:user][:lecture].select { |k, v| v == '1' }.keys.map(&:to_i) + end - def clean_up_notifications - # delete all of the user's notifications if he does not want them - # remove all notification related not related to subscribed courses - # or lectures - subscribed_teachables = @courses + @lectures - irrelevant_notifications = @user.notifications.select do |n| - n.teachable.present? && !n.teachable.in?(subscribed_teachables) + def clean_up_notifications + # delete all of the user's notifications if he does not want them + # remove all notification related not related to subscribed courses + # or lectures + subscribed_teachables = @courses + @lectures + irrelevant_notifications = @user.notifications.select do |n| + n.teachable.present? && !n.teachable.in?(subscribed_teachables) + end + Notification.where(id: irrelevant_notifications.map(&:id)).delete_all end - Notification.where(id: irrelevant_notifications.map(&:id)).delete_all - end - # if user unsubscribed the lecture the current lecture cookie refers to, - # set the lectures cookie to nil - def update_lecture_cookie - unless @current_lecture.in?(@user.lectures) - cookies[:current_lecture_id] = nil + # if user unsubscribed the lecture the current lecture cookie refers to, + # set the lectures cookie to nil + def update_lecture_cookie + unless @current_lecture.in?(@user.lectures) + cookies[:current_lecture_id] = nil + end end - end - # stop the update if any of passphrases for newly subscribed - # lectures is incorrect - def check_passphrases - @errors = {} - restricted_lectures = Lecture.where(id: lecture_ids) - .select do |l| - l.in?(l.course - .to_be_authorized_lectures(current_user)) - end - restricted_lectures.each do |l| - given_passphrase = params[:user][:pass_lecture][l.id.to_s] - unless given_passphrase == l.passphrase - @errors[:passphrase] ||= [] - @errors[:passphrase].push l.id + # stop the update if any of passphrases for newly subscribed + # lectures is incorrect + def check_passphrases + @errors = {} + restricted_lectures = Lecture.where(id: lecture_ids) + .select do |l| + l.in?(l.course + .to_be_authorized_lectures(current_user)) + end + restricted_lectures.each do |l| + given_passphrase = params[:user][:pass_lecture][l.id.to_s] + unless given_passphrase == l.passphrase + @errors[:passphrase] ||= [] + @errors[:passphrase].push l.id + end end end - end end diff --git a/app/controllers/programs_controller.rb b/app/controllers/programs_controller.rb index 096e082fb..babb0eb30 100644 --- a/app/controllers/programs_controller.rb +++ b/app/controllers/programs_controller.rb @@ -3,47 +3,46 @@ class ProgramsController < ApplicationController before_action :set_program, except: [:new, :create] authorize_resource except: [:new, :create] - def current_ability @current_ability ||= ProgramAbility.new(current_user) end - def edit - end + def edit + end - def new - @program = Program.new(subject_id: params[:subject_id].to_i) + def new + @program = Program.new(subject_id: params[:subject_id].to_i) authorize! :new, @program - end + end - def update - @program.update(program_params) - redirect_to classification_path - end + def update + @program.update(program_params) + redirect_to classification_path + end - def create - @program = Program.new(program_params) - @program.subject_id = params[:program][:subject_id].to_i + def create + @program = Program.new(program_params) + @program.subject_id = params[:program][:subject_id].to_i authorize! :create, @program - @program.save - redirect_to classification_path - end + @program.save + redirect_to classification_path + end - def destroy - @program.destroy - redirect_to classification_path - end + def destroy + @program.destroy + redirect_to classification_path + end private - def set_program - @program = Program.find_by_id(params[:id]) - return if @program.present? + def set_program + @program = Program.find_by_id(params[:id]) + return if @program.present? - redirect_to root_path, alert: I18n.t('controllers.no_program') - end + redirect_to root_path, alert: I18n.t('controllers.no_program') + end - def program_params - params.require(:program).permit(*Program.globalize_attribute_names) - end -end \ No newline at end of file + def program_params + params.require(:program).permit(*Program.globalize_attribute_names) + end +end diff --git a/app/controllers/questions_controller.rb b/app/controllers/questions_controller.rb index 7daaf3936..1444e3ba1 100644 --- a/app/controllers/questions_controller.rb +++ b/app/controllers/questions_controller.rb @@ -16,6 +16,7 @@ def edit def update return if @errors + @success = true if @question.update(question_params) if question_params[:question_sort] == 'free' answer = @question.answers.first @@ -53,14 +54,14 @@ def reassign def set_solution_type content = if params[:type] == 'MampfExpression' - MampfExpression.trivial_instance - elsif params[:type] == 'MampfMatrix' - MampfMatrix.trivial_instance - elsif params[:type] == 'MampfTuple' - MampfTuple.trivial_instance - elsif params[:type] == 'MampfSet' - MampfSet.trivial_instance - end + MampfExpression.trivial_instance + elsif params[:type] == 'MampfMatrix' + MampfMatrix.trivial_instance + elsif params[:type] == 'MampfTuple' + MampfTuple.trivial_instance + elsif params[:type] == 'MampfSet' + MampfSet.trivial_instance + end @solution = Solution.new(content) end @@ -76,33 +77,37 @@ def render_question_parameters private - def set_question - @question = Question.find_by_id(params[:id]) - return if @question.present? - redirect_to :root, alert: I18n.t('controllers.no_question') - end + def set_question + @question = Question.find_by_id(params[:id]) + return if @question.present? - def set_quizzes - @quizzes = params[:question].select { |k, v| v == '1' && k.start_with?('quiz-') } - .keys.map { |k| k.remove('quiz-').to_i } - end + redirect_to :root, alert: I18n.t('controllers.no_question') + end - def check_solution_errors - return unless params[:question][:solution_error].present? - @errors = ActiveModel::Errors.new(@question) - @errors.add(:base, params[:question][:solution_error]) - end + def set_quizzes + @quizzes = params[:question].select { |k, v| + v == '1' && k.start_with?('quiz-') + } + .keys.map { |k| k.remove('quiz-').to_i } + end + + def check_solution_errors + return unless params[:question][:solution_error].present? - def question_params - result = params.require(:question) - .permit(:label, :text, :type, :hint, :level, - :question_sort, :independent, :vertex_id, - :solution_type, - solution_content: {}) - if result[:solution_type] && result[:solution_content] - result[:solution] = Solution.from_hash(result[:solution_type], - result[:solution_content]) + @errors = ActiveModel::Errors.new(@question) + @errors.add(:base, params[:question][:solution_error]) + end + + def question_params + result = params.require(:question) + .permit(:label, :text, :type, :hint, :level, + :question_sort, :independent, :vertex_id, + :solution_type, + solution_content: {}) + if result[:solution_type] && result[:solution_content] + result[:solution] = Solution.from_hash(result[:solution_type], + result[:solution_content]) + end + result.except(:solution_type, :solution_content) end - result.except(:solution_type, :solution_content) - end end diff --git a/app/controllers/quiz_certificates_controller.rb b/app/controllers/quiz_certificates_controller.rb index 572dca0a8..0f611e18d 100644 --- a/app/controllers/quiz_certificates_controller.rb +++ b/app/controllers/quiz_certificates_controller.rb @@ -22,31 +22,35 @@ def validate private - def set_certificate - @certificate = QuizCertificate.find_by_id(params[:id]) - return if @certificate.present? - redirect_to :root, alert: I18n.t('controllers.no_certificate') - end + def set_certificate + @certificate = QuizCertificate.find_by_id(params[:id]) + return if @certificate.present? - def check_if_claimed - return unless @certificate.user - redirect_to :root, alert: I18n.t('controllers.certificate_already_claimed') - end + redirect_to :root, alert: I18n.t('controllers.no_certificate') + end - def certificate_params - params.permit(:code, :lecture_id) - end + def check_if_claimed + return unless @certificate.user + + redirect_to :root, + alert: I18n.t('controllers.certificate_already_claimed') + end - def set_locale_by_quiz - return unless @certificate - quiz_locale = @certificate.quiz.locale_with_inheritance - I18n.locale = quiz_locale || current_user.locale || + def certificate_params + params.permit(:code, :lecture_id) + end + + def set_locale_by_quiz + return unless @certificate + + quiz_locale = @certificate.quiz.locale_with_inheritance + I18n.locale = quiz_locale || current_user.locale || I18n.default_locale - end + end - def set_locale_by_lecture - @lecture = Lecture.find_by_id(certificate_params[:lecture_id]) - I18n.locale = @lecture&.locale_with_inheritance || current_user.locale || + def set_locale_by_lecture + @lecture = Lecture.find_by_id(certificate_params[:lecture_id]) + I18n.locale = @lecture&.locale_with_inheritance || current_user.locale || I18n.default_locale - end -end \ No newline at end of file + end +end diff --git a/app/controllers/quizzes_controller.rb b/app/controllers/quizzes_controller.rb index 3e92d5c6c..3cf4ead9c 100644 --- a/app/controllers/quizzes_controller.rb +++ b/app/controllers/quizzes_controller.rb @@ -98,64 +98,69 @@ def render_vertex_quizzable private - def set_quiz - @quiz = Quiz.find_by_id(params[:id]) - return if @quiz.present? - redirect_to :root, alert: I18n.t('controllers.no_quiz') - end + def set_quiz + @quiz = Quiz.find_by_id(params[:id]) + return if @quiz.present? - def init_values - quiz_round_params = if params[:question].present? && - params[:question][:solution_type].present? - params[:question] - else - params - end - if user_signed_in? && current_user.study_participant - quiz_round_params[:study_participant] = current_user.anonymized_id + redirect_to :root, alert: I18n.t('controllers.no_quiz') end - quiz_round_params[:save_probe] = - if !user_signed_in? - true - elsif current_user.admin? - false - elsif current_user.in?(Quiz.find(params[:id]).editors_with_inheritance) - false + + def init_values + quiz_round_params = if params[:question].present? && + params[:question][:solution_type].present? + params[:question] else - true + params end - @quiz_round = QuizRound.new(quiz_round_params) - end + if user_signed_in? && current_user.study_participant + quiz_round_params[:study_participant] = current_user.anonymized_id + end + quiz_round_params[:save_probe] = + if !user_signed_in? + true + elsif current_user.admin? + false + elsif current_user.in?(Quiz.find(params[:id]).editors_with_inheritance) + false + else + true + end + @quiz_round = QuizRound.new(quiz_round_params) + end - def quiz_params - params.require(:quiz).permit(:label, :root, :level, :id_js) - end + def quiz_params + params.require(:quiz).permit(:label, :root, :level, :id_js) + end - def check_accessibility - return if @quiz.sort == 'RandomQuiz' - return if user_signed_in? && @quiz.visible_for_user?(current_user) - return if !user_signed_in? && @quiz.free? - redirect_to :root, alert: I18n.t('controllers.no_quiz_access') - end + def check_accessibility + return if @quiz.sort == 'RandomQuiz' + return if user_signed_in? && @quiz.visible_for_user?(current_user) + return if !user_signed_in? && @quiz.free? - def check_vertex_accessibility - return if @quiz.sort == 'RandomQuiz' - if user_signed_in? - return if current_user.in?(@quiz.editors_with_inheritance) - return if current_user.admin - return if @quiz.quizzables_visible_for_user?(current_user) + redirect_to :root, alert: I18n.t('controllers.no_quiz_access') end - return if !user_signed_in? && @quiz.quizzables_free? - redirect_to :root, alert: I18n.t('controllers.no_quiz_vertex_access') - end - def check_errors - return if @quiz.sort == 'RandomQuiz' - return unless @quiz.find_errors&.any? - redirect_to :root, alert: I18n.t('controllers.quiz_has_error') - end + def check_vertex_accessibility + return if @quiz.sort == 'RandomQuiz' - def store_access - ConsumptionSaver.perform_async(@quiz.id, 'browser', 'quiz') - end + if user_signed_in? + return if current_user.in?(@quiz.editors_with_inheritance) + return if current_user.admin + return if @quiz.quizzables_visible_for_user?(current_user) + end + return if !user_signed_in? && @quiz.quizzables_free? + + redirect_to :root, alert: I18n.t('controllers.no_quiz_vertex_access') + end + + def check_errors + return if @quiz.sort == 'RandomQuiz' + return unless @quiz.find_errors&.any? + + redirect_to :root, alert: I18n.t('controllers.quiz_has_error') + end + + def store_access + ConsumptionSaver.perform_async(@quiz.id, 'browser', 'quiz') + end end diff --git a/app/controllers/readers_controller.rb b/app/controllers/readers_controller.rb index 6bd1587ab..362e5cd86 100644 --- a/app/controllers/readers_controller.rb +++ b/app/controllers/readers_controller.rb @@ -5,6 +5,7 @@ class ReadersController < ApplicationController def update @thread = Commontator::Thread.find_by_id(reader_params[:thread_id]) return unless @thread + @reader = Reader.find_or_create_by(user: current_user, thread: @thread) @reader.touch @@ -32,7 +33,7 @@ def update_all private - def reader_params - params.permit(:thread_id) - end -end \ No newline at end of file + def reader_params + params.permit(:thread_id) + end +end diff --git a/app/controllers/referrals_controller.rb b/app/controllers/referrals_controller.rb index c058bf57e..4d07fa221 100644 --- a/app/controllers/referrals_controller.rb +++ b/app/controllers/referrals_controller.rb @@ -15,6 +15,7 @@ def update # be affected; links are changed *globally* update_item if Item.find_by_id(@item_id)&.sort == 'link' return if @errors.present? + @referral.update(updated_params) @errors = @referral.errors unless @referral.valid? end @@ -25,12 +26,12 @@ def edit # otherwise load all items in the referral's item's medium scope # that the user can choose from in the item dropdown menu @item_selection = if @referral.item.sort == 'link' - Item.where(medium: nil) - .map { |i| [i.description, i.id] } - else - @referral.item.medium.teachable.media_scope - .media_items_with_inheritance - end + Item.where(medium: nil) + .map { |i| [i.description, i.id] } + else + @referral.item.medium.teachable.media_scope + .media_items_with_inheritance + end @item = Item.new(sort: 'link') end @@ -70,36 +71,36 @@ def list_items private - def set_referral - @referral = Referral.find(params[:id]) - end + def set_referral + @referral = Referral.find(params[:id]) + end - def set_basics - @item_id = params[:referral][:item_id].to_i - end + def set_basics + @item_id = params[:referral][:item_id].to_i + end - def referral_params - # clone referral params in order to convert start and end time to proper - # TimeStamp instances - filter = params.require(:referral).permit(:medium_id, :item_id, :start_time, - :end_time, :description, :link, - :explanation, :ref_id).clone - filter[:start_time] = TimeStamp.new(time_string: filter[:start_time]) - filter[:end_time] = TimeStamp.new(time_string: filter[:end_time]) - filter - end + def referral_params + # clone referral params in order to convert start and end time to proper + # TimeStamp instances + filter = params.require(:referral).permit(:medium_id, :item_id, :start_time, + :end_time, :description, :link, + :explanation, :ref_id).clone + filter[:start_time] = TimeStamp.new(time_string: filter[:start_time]) + filter[:end_time] = TimeStamp.new(time_string: filter[:end_time]) + filter + end - def update_item - item = Item.find(@item_id) - item.update(link: referral_params[:link], - description: referral_params[:description]) - @errors = item.errors unless item.valid? - end + def update_item + item = Item.find(@item_id) + item.update(link: referral_params[:link], + description: referral_params[:description]) + @errors = item.errors unless item.valid? + end - def updated_params - { medium_id: referral_params[:medium_id], item_id: @item_id, - explanation: referral_params[:explanation], - start_time: referral_params[:start_time], - end_time: referral_params[:end_time] } - end + def updated_params + { medium_id: referral_params[:medium_id], item_id: @item_id, + explanation: referral_params[:explanation], + start_time: referral_params[:start_time], + end_time: referral_params[:end_time] } + end end diff --git a/app/controllers/registrations_controller.rb b/app/controllers/registrations_controller.rb index ec884e78d..0cd93ae14 100644 --- a/app/controllers/registrations_controller.rb +++ b/app/controllers/registrations_controller.rb @@ -7,10 +7,11 @@ class RegistrationsController < Devise::RegistrationsController def verify_captcha return true unless ENV['USE_CAPTCHA_SERVICE'] + begin uri = URI.parse(ENV['CAPTCHA_VERIFY_URL']) - data = { message:params["frc-captcha-solution"], - application_token:ENV['CAPTCHA_APPLICATION_TOKEN'] } + data = { message: params["frc-captcha-solution"], + application_token: ENV['CAPTCHA_APPLICATION_TOKEN'] } header = { 'Content-Type': 'text/json' } http = Net::HTTP.new(uri.host, uri.port) http.use_ssl = true if ENV['CAPTCHA_VERIFY_URL'].include?('https') @@ -19,7 +20,7 @@ def verify_captcha # Send the request response = http.request(request) - answer = JSON.parse(response.body) + answer = JSON.parse(response.body) return true if answer["message"] == "verified" rescue end @@ -41,19 +42,25 @@ def destroy password_correct = resource.valid_password?(deletion_params[:password]) if !password_correct set_flash_message :alert, :password_incorrect - respond_with_navigational(resource){ redirect_to after_sign_up_path_for(resource_name) } + respond_with_navigational(resource) { + redirect_to after_sign_up_path_for(resource_name) + } return end success = resource.archive_and_destroy(deletion_params[:archive_name]) if !success set_flash_message :alert, :not_destroyed - respond_with_navigational(resource){ redirect_to after_sign_up_path_for(resource_name) } + respond_with_navigational(resource) { + redirect_to after_sign_up_path_for(resource_name) + } return end Devise.sign_out_all_scopes ? sign_out : sign_out(resource_name) set_flash_message :notice, :destroyed yield resource if block_given? - respond_with_navigational(resource){ redirect_to after_sign_out_path_for(resource_name) } + respond_with_navigational(resource) { + redirect_to after_sign_out_path_for(resource_name) + } end def after_sign_up_path_for(resource) @@ -62,17 +69,17 @@ def after_sign_up_path_for(resource) private - def check_registration_limit - if User.where("users.confirmed_at is NULL and users.created_at > '#{(DateTime.now()-(ENV['MAMPF_REGISTRATION_TIMEFRAME']||15).to_i.minutes)}'").count > (ENV['MAMPF_MAX_REGISTRATION_PER_TIMEFRAME'] || 40).to_i - self.resource = resource_class.new devise_parameter_sanitizer.sanitize(:sign_up) - resource.validate # Look for any other validation errors besides reCAPTCHA - set_flash_message :alert, :too_many_registrations - set_minimum_password_length - respond_with_navigational(resource) { render :new } + def check_registration_limit + if User.where("users.confirmed_at is NULL and users.created_at > '#{(DateTime.now() - (ENV['MAMPF_REGISTRATION_TIMEFRAME'] || 15).to_i.minutes)}'").count > (ENV['MAMPF_MAX_REGISTRATION_PER_TIMEFRAME'] || 40).to_i + self.resource = resource_class.new devise_parameter_sanitizer.sanitize(:sign_up) + resource.validate # Look for any other validation errors besides reCAPTCHA + set_flash_message :alert, :too_many_registrations + set_minimum_password_length + respond_with_navigational(resource) { render :new } + end end - end - def deletion_params - params.permit(:archive_name, :password) - end + def deletion_params + params.permit(:archive_name, :password) + end end diff --git a/app/controllers/remarks_controller.rb b/app/controllers/remarks_controller.rb index c1529482b..9d95e9779 100644 --- a/app/controllers/remarks_controller.rb +++ b/app/controllers/remarks_controller.rb @@ -41,18 +41,19 @@ def cancel_remark_basics private - def set_remark - @remark = Remark.find_by_id(params[:id]) - return if @remark.present? - redirect_to remarks_path, alert: I18n.t('controllers.no_remark') - end + def set_remark + @remark = Remark.find_by_id(params[:id]) + return if @remark.present? - def set_quizzes - @quizzes = params[:remark].select { |_k, v| v == '1' }.keys - .map { |k| k.remove('quiz-').to_i } - end + redirect_to remarks_path, alert: I18n.t('controllers.no_remark') + end - def remark_params - params.require(:remark).permit(:text, :text_input, :type) - end + def set_quizzes + @quizzes = params[:remark].select { |_k, v| v == '1' }.keys + .map { |k| k.remove('quiz-').to_i } + end + + def remark_params + params.require(:remark).permit(:text, :text_input, :type) + end end diff --git a/app/controllers/search_controller.rb b/app/controllers/search_controller.rb index 662b36536..ef9d49636 100644 --- a/app/controllers/search_controller.rb +++ b/app/controllers/search_controller.rb @@ -20,35 +20,37 @@ def index # (taking into account his preferences and subscribed courses) @filtered_tags = current_user.filter_tags(@tags) return unless @tags.empty? + # determine tags whose title is apartial match find_similar_tags end private - def check_for_consent - redirect_to consent_profile_path unless current_user.consents - end + def check_for_consent + redirect_to consent_profile_path unless current_user.consents + end - def set_search_string - @search_string = params[:search] - end + def set_search_string + @search_string = params[:search] + end + + # make sure the seacrh string is not blank and consists of at least + # two letters + def sanitize_search_string + if @search_string.nil? + redirect_back fallback_location: root_path, + alert: I18n.t('controllers.no_search_term') + return + end + return if @search_string.length > 1 - # make sure the seacrh string is not blank and consists of at least - # two letters - def sanitize_search_string - if @search_string.nil? redirect_back fallback_location: root_path, - alert: I18n.t('controllers.no_search_term') - return + alert: I18n.t('controllers.search_term_short') end - return if @search_string.length > 1 - redirect_back fallback_location: root_path, - alert: I18n.t('controllers.search_term_short') - end - def find_similar_tags - @similar_tags = Tag.similar_tags(@search_string) - @filtered_similar_tags = current_user.filter_tags(@similar_tags) - end + def find_similar_tags + @similar_tags = Tag.similar_tags(@search_string) + @filtered_similar_tags = current_user.filter_tags(@similar_tags) + end end diff --git a/app/controllers/sections_controller.rb b/app/controllers/sections_controller.rb index 00103d105..f7a87b997 100644 --- a/app/controllers/sections_controller.rb +++ b/app/controllers/sections_controller.rb @@ -56,46 +56,48 @@ def display private - def set_section - @section = Section.find_by_id(params[:id]) - return if @section.present? - redirect_to :root, alert: I18n.t('controllers.no_section') - end + def set_section + @section = Section.find_by_id(params[:id]) + return if @section.present? - def section_params - params.require(:section).permit(:title, :display_number, :chapter_id, - :details, :hidden, - tag_ids: [], lesson_ids: []) - end + redirect_to :root, alert: I18n.t('controllers.no_section') + end - # inserts the section in the correct position if predecessor is given, - # otherwise just saves - def insert_or_save - position = params[:section][:predecessor] - if position.present? - @section.insert_at(position.to_i + 1) - else - @section.save + def section_params + params.require(:section).permit(:title, :display_number, :chapter_id, + :details, :hidden, + tag_ids: [], lesson_ids: []) end - end - # updates the position of the section if predecessor is given - def update_position - predecessor = params[:section][:predecessor] - return unless predecessor.present? - position = predecessor.to_i - if position > @section.position && @old_chapter == @section.chapter - position -= 1 + # inserts the section in the correct position if predecessor is given, + # otherwise just saves + def insert_or_save + position = params[:section][:predecessor] + if position.present? + @section.insert_at(position.to_i + 1) + else + @section.save + end end - @section.insert_at(position + 1) - end - def update_tags_order - tags_order = params[:section][:tag_ids].map(&:to_i) - [0] - SectionTagJoin.acts_as_list_no_update do - @section.section_tag_joins.each do |st| - st.update(tag_position: tags_order.index(st.tag_id)) + # updates the position of the section if predecessor is given + def update_position + predecessor = params[:section][:predecessor] + return unless predecessor.present? + + position = predecessor.to_i + if position > @section.position && @old_chapter == @section.chapter + position -= 1 + end + @section.insert_at(position + 1) + end + + def update_tags_order + tags_order = params[:section][:tag_ids].map(&:to_i) - [0] + SectionTagJoin.acts_as_list_no_update do + @section.section_tag_joins.each do |st| + st.update(tag_position: tags_order.index(st.tag_id)) + end end end - end end diff --git a/app/controllers/sessions_controller.rb b/app/controllers/sessions_controller.rb index ccb2e2f3c..ad3178f19 100644 --- a/app/controllers/sessions_controller.rb +++ b/app/controllers/sessions_controller.rb @@ -1,8 +1,7 @@ class SessionsController < Devise::SessionsController - # remove devise's flash message for succesful sign_in def create super flash.clear end -end \ No newline at end of file +end diff --git a/app/controllers/subjects_controller.rb b/app/controllers/subjects_controller.rb index 1aec93c3f..6cd036d20 100644 --- a/app/controllers/subjects_controller.rb +++ b/app/controllers/subjects_controller.rb @@ -12,13 +12,13 @@ def new authorize! :new, @subject end - def edit - end + def edit + end - def update - @subject.update(subject_params) - redirect_to classification_path - end + def update + @subject.update(subject_params) + redirect_to classification_path + end def create @subject = Subject.new(subject_params) @@ -34,14 +34,14 @@ def destroy private - def set_subject - @subject = Subject.find_by_id(params[:id]) - return if @subject.present? + def set_subject + @subject = Subject.find_by_id(params[:id]) + return if @subject.present? - redirect_to root_path, alert: I18n.t('controllers.no_answer') - end + redirect_to root_path, alert: I18n.t('controllers.no_answer') + end - def subject_params - params.require(:subject).permit(*Subject.globalize_attribute_names) - end -end \ No newline at end of file + def subject_params + params.require(:subject).permit(*Subject.globalize_attribute_names) + end +end diff --git a/app/controllers/submissions_controller.rb b/app/controllers/submissions_controller.rb index 442b3ad1e..ad4422df9 100644 --- a/app/controllers/submissions_controller.rb +++ b/app/controllers/submissions_controller.rb @@ -23,13 +23,13 @@ def index @current_assignments = @lecture.current_assignments @previous_assignments = @lecture.previous_assignments @old_assignments = @assignments.expired.order('deadline DESC') - - @previous_assignments + @previous_assignments @future_assignments = @assignments.active.order(:deadline) - - @current_assignments + @current_assignments end def new - @submission = Submission.new + @submission = Submission.new @submission.assignment = @assignment set_submission_locale end @@ -38,7 +38,8 @@ def edit end def update - return if @too_late + return if @too_late + old_manuscript_data = @submission.manuscript_data @old_filename = @submission.manuscript_filename if submission_manuscript_params[:manuscript].present? @@ -47,6 +48,7 @@ def update .metadata, :manuscript) return if @errors.present? + @submission.save @errors = @submission.errors return unless @submission.valid? @@ -67,11 +69,12 @@ def update end def create - @submission = Submission.new(submission_create_params) + @submission = Submission.new(submission_create_params) @lecture = @submission&.assignment&.lecture set_submission_locale @too_late = @submission.not_updatable? - return if @too_late + return if @too_late + if submission_manuscript_params[:manuscript].present? @submission.manuscript = submission_manuscript_params[:manuscript] @errors = @submission.check_file_properties(@submission.manuscript @@ -84,14 +87,17 @@ def create @assignment = @submission.assignment @errors = @submission.errors return unless @submission.valid? + send_invitation_emails @submission.update(last_modification_by_users_at: Time.now) return unless @submission.manuscript + send_upload_email(User.where(id: current_user.id)) end def destroy return if @too_late + @submission.destroy end @@ -124,6 +130,7 @@ def join def leave return if @too_late + if @submission.users.count == 1 @error = I18n.t('submission.no_partners_no_leave') return @@ -141,8 +148,8 @@ def cancel_new def show_manuscript if @submission && @submission.manuscript send_file @submission.manuscript.to_io, - type: @submission.manuscript_mime_type, - disposition: @disposition, + type: @submission.manuscript_mime_type, + disposition: @disposition, filename: @submission.manuscript_filename elsif @submission redirect_to :start, alert: t('submission.no_manuscript_yet') @@ -165,20 +172,20 @@ def show_correction end def refresh_token - @submission.update(token: Submission.generate_token) + @submission.update(token: Submission.generate_token) end def enter_invitees - @too_late = @submission.assignment.totally_expired? + @too_late = @submission.assignment.totally_expired? end def invite - if @too_late - render :create - return - end - send_invitation_emails - render :create + if @too_late + render :create + return + end + send_invitation_emails + render :create end def edit_correction @@ -188,12 +195,13 @@ def cancel_edit_correction end def add_correction - if correction_params[:correction].present? + if correction_params[:correction].present? @submission.correction = correction_params[:correction] @errors = @submission.check_file_properties_any(@submission.correction .metadata, - :correction) + :correction) return if @errors.present? + @submission.save @errors = @submission.errors return unless @submission.valid? @@ -201,6 +209,7 @@ def add_correction @submission.update(correction_params) @errors = @submission.errors return if @errors.present? + send_correction_upload_email(@submission.users) end @@ -235,212 +244,220 @@ def reject private - def set_submission - @submission = Submission.find_by_id(params[:id]) - @assignment = @submission&.assignment - @lecture = @assignment&.lecture - set_submission_locale - return if @submission - flash[:alert] = I18n.t('controllers.no_submission') - render js: "window.location='#{root_path}'" - end + def set_submission + @submission = Submission.find_by_id(params[:id]) + @assignment = @submission&.assignment + @lecture = @assignment&.lecture + set_submission_locale + return if @submission - def submission_create_params - params.require(:submission).permit(:tutorial_id, :assignment_id) - end + flash[:alert] = I18n.t('controllers.no_submission') + render js: "window.location='#{root_path}'" + end - # disallow modification of assignment - def submission_update_params - params.require(:submission).permit(:tutorial_id) - end + def submission_create_params + params.require(:submission).permit(:tutorial_id, :assignment_id) + end - # disallow modification of assignment - def submission_manuscript_params - params.require(:submission).permit(:manuscript) - end + # disallow modification of assignment + def submission_update_params + params.require(:submission).permit(:tutorial_id) + end - def set_assignment - @assignment = Assignment.find_by_id(params[:assignment_id]) - @lecture = @assignment&.lecture - set_submission_locale - return if @assignment - flash[:alert] = I18n.t('controllers.no_assignment') - render js: "window.location='#{root_path}'" - return - end + # disallow modification of assignment + def submission_manuscript_params + params.require(:submission).permit(:manuscript) + end - def set_lecture - @lecture = Lecture.find_by_id(params[:id]) - set_submission_locale and return if @lecture - redirect_to :root, alert: I18n.t('controllers.no_lecture') - end + def set_assignment + @assignment = Assignment.find_by_id(params[:assignment_id]) + @lecture = @assignment&.lecture + set_submission_locale + return if @assignment - def set_too_late - @too_late = @submission.not_updatable? - end + flash[:alert] = I18n.t('controllers.no_assignment') + render js: "window.location='#{root_path}'" + return + end - def set_submission_locale - I18n.locale = @lecture&.locale_with_inheritance || current_user.locale || + def set_lecture + @lecture = Lecture.find_by_id(params[:id]) + set_submission_locale and return if @lecture + + redirect_to :root, alert: I18n.t('controllers.no_lecture') + end + + def set_too_late + @too_late = @submission.not_updatable? + end + + def set_submission_locale + I18n.locale = @lecture&.locale_with_inheritance || current_user.locale || I18n.default_locale - end + end - def join_params - params.require(:join).permit(:code, :assignment_id) - end + def join_params + params.require(:join).permit(:code, :assignment_id) + end - def invitation_params - params.require(:submission).permit(invitee_ids: []) - end + def invitation_params + params.require(:submission).permit(invitee_ids: []) + end - def correction_params - params.require(:submission).permit(:correction) - end + def correction_params + params.require(:submission).permit(:correction) + end - def move_params - params.require(:submission).permit(:tutorial_id) - end + def move_params + params.require(:submission).permit(:tutorial_id) + end - def send_invitation_emails - invitees = User.where(id: invitation_params[:invitee_ids]) - invitees.each do |i| - NotificationMailer.with(recipient: i, - locale: i.locale, - assignment: @assignment, - code: @submission.token, - issuer: current_user) - .submission_invitation_email.deliver_later - end - @submission.update(invited_user_ids: @submission.invited_user_ids | - invitees.pluck(:id)) - end + def send_invitation_emails + invitees = User.where(id: invitation_params[:invitee_ids]) + invitees.each do |i| + NotificationMailer.with(recipient: i, + locale: i.locale, + assignment: @assignment, + code: @submission.token, + issuer: current_user) + .submission_invitation_email.deliver_later + end + @submission.update(invited_user_ids: @submission.invited_user_ids | + invitees.pluck(:id)) + end - def send_upload_email(users) - users.email_for_submission_upload.each do |u| - NotificationMailer.with(recipient: u, - locale: u.locale, - submission: @submission, - uploader: current_user, - filename: @submission.manuscript_filename) - .submission_upload_email.deliver_later + def send_upload_email(users) + users.email_for_submission_upload.each do |u| + NotificationMailer.with(recipient: u, + locale: u.locale, + submission: @submission, + uploader: current_user, + filename: @submission.manuscript_filename) + .submission_upload_email.deliver_later + end end - end - def send_upload_removal_email(users) - users.email_for_submission_removal.each do |u| - NotificationMailer.with(recipient: u, - locale: u.locale, - submission: @submission, - remover: current_user, - filename: @old_filename) - .submission_upload_removal_email.deliver_later + def send_upload_removal_email(users) + users.email_for_submission_removal.each do |u| + NotificationMailer.with(recipient: u, + locale: u.locale, + submission: @submission, + remover: current_user, + filename: @old_filename) + .submission_upload_removal_email.deliver_later + end end - end - def send_correction_upload_email(users) - users.email_for_correction_upload.each do |u| - NotificationMailer.with(recipient: u, - locale: u.locale, - submission: @submission, - tutor: current_user) - .correction_upload_email.deliver_later + def send_correction_upload_email(users) + users.email_for_correction_upload.each do |u| + NotificationMailer.with(recipient: u, + locale: u.locale, + submission: @submission, + tutor: current_user) + .correction_upload_email.deliver_later + end end - end - def send_acceptance_email(users) - users.email_for_submission_decision.each do |u| - NotificationMailer.with(recipient: u, - locale: u.locale, - submission: @submission) - .submission_acceptance_email.deliver_later + def send_acceptance_email(users) + users.email_for_submission_decision.each do |u| + NotificationMailer.with(recipient: u, + locale: u.locale, + submission: @submission) + .submission_acceptance_email.deliver_later + end end - end - def send_rejection_email(users) - users.email_for_submission_decision.each do |u| - NotificationMailer.with(recipient: u, - locale: u.locale, - submission: @submission) - .submission_rejection_email.deliver_later + def send_rejection_email(users) + users.email_for_submission_decision.each do |u| + NotificationMailer.with(recipient: u, + locale: u.locale, + submission: @submission) + .submission_rejection_email.deliver_later + end end - end - def check_code_validity - if !@submission && @assignment - @error = I18n.t('submission.invalid_code_for_assignment', - assignment: @assignment.title) - elsif !@submission - @error = I18n.t('submission.invalid_code') - elsif @assignment&.totally_expired? - @error = I18n.t('submission.assignment_expired') - elsif @submission.correction - @error = I18n.t('submission.already_corrected') - elsif current_user.in?(@submission.users) - @error = I18n.t('submission.already_in') - elsif !@submission.tutorial.lecture.in?(current_user.lectures) - @error = I18n.t('submission.lecture_not_subscribed') + def check_code_validity + if !@submission && @assignment + @error = I18n.t('submission.invalid_code_for_assignment', + assignment: @assignment.title) + elsif !@submission + @error = I18n.t('submission.invalid_code') + elsif @assignment&.totally_expired? + @error = I18n.t('submission.assignment_expired') + elsif @submission.correction + @error = I18n.t('submission.already_corrected') + elsif current_user.in?(@submission.users) + @error = I18n.t('submission.already_in') + elsif !@submission.tutorial.lecture.in?(current_user.lectures) + @error = I18n.t('submission.lecture_not_subscribed') + end end - end - def check_code_and_join - check_code_validity - unless @error - @join = UserSubmissionJoin.new(user: current_user, - submission: @submission) - @join.save - if @join.valid? - @submission.update(last_modification_by_users_at: Time.now) - send_join_email - remove_invitee_status - else - @error = @join.errors[:base].join(', ') - end + def check_code_and_join + check_code_validity + unless @error + @join = UserSubmissionJoin.new(user: current_user, + submission: @submission) + @join.save + if @join.valid? + @submission.update(last_modification_by_users_at: Time.now) + send_join_email + remove_invitee_status + else + @error = @join.errors[:base].join(', ') + end + end end - end - def send_join_email - (@submission.users.email_for_submission_join - [current_user]).each do |u| - NotificationMailer.with(recipient: u, - locale: u.locale, - submission: @submission, - user: current_user) - .submission_join_email.deliver_later + def send_join_email + (@submission.users.email_for_submission_join - [current_user]).each do |u| + NotificationMailer.with(recipient: u, + locale: u.locale, + submission: @submission, + user: current_user) + .submission_join_email.deliver_later + end end - end - def send_leave_email - (@submission.users.email_for_submission_leave - [current_user]).each do |u| - NotificationMailer.with(recipient: u, - locale: u.locale, - submission: @submission, - user: current_user) - .submission_leave_email.deliver_later + def send_leave_email + (@submission.users.email_for_submission_leave - [current_user]).each do |u| + NotificationMailer.with(recipient: u, + locale: u.locale, + submission: @submission, + user: current_user) + .submission_leave_email.deliver_later + end end - end - def remove_invitee_status - @submission.update(invited_user_ids: @submission.invited_user_ids - - [current_user.id]) - end + def remove_invitee_status + @submission.update(invited_user_ids: @submission.invited_user_ids - + [current_user.id]) + end - def check_student_status - return if current_user.proper_student_in?(@lecture) - redirect_to :root, alert: I18n.t('controllers.no_student_status_in_lecture') - end + def check_student_status + return if current_user.proper_student_in?(@lecture) - def check_if_tutorials - return if @lecture.tutorials.any? - redirect_to :root, alert: I18n.t('controllers.no_tutorials_in_lecture') - end + redirect_to :root, + alert: I18n.t('controllers.no_student_status_in_lecture') + end - def check_if_assignments - return if @lecture.assignments.any? - redirect_to :root, alert: I18n.t('controllers.no_assignments_in_lecture') - end + def check_if_tutorials + return if @lecture.tutorials.any? - def set_disposition - @disposition = params[:download] == 'true' ? 'attachment' : 'inline' - accepted = @submission.assignment.accepted_file_type - return unless accepted.in?(Assignment.non_inline_file_types) - @disposition = 'attachment' - end -end \ No newline at end of file + redirect_to :root, alert: I18n.t('controllers.no_tutorials_in_lecture') + end + + def check_if_assignments + return if @lecture.assignments.any? + + redirect_to :root, alert: I18n.t('controllers.no_assignments_in_lecture') + end + + def set_disposition + @disposition = params[:download] == 'true' ? 'attachment' : 'inline' + accepted = @submission.assignment.accepted_file_type + return unless accepted.in?(Assignment.non_inline_file_types) + + @disposition = 'attachment' + end +end diff --git a/app/controllers/tags_controller.rb b/app/controllers/tags_controller.rb index a27e4eafa..574a42d84 100644 --- a/app/controllers/tags_controller.rb +++ b/app/controllers/tags_controller.rb @@ -11,7 +11,6 @@ class TagsController < ApplicationController :render_tag_title] layout 'administration' - def current_ability @current_ability ||= TagAbility.new(current_user) end @@ -62,6 +61,7 @@ def new def update # first, check if errors from check_permission callback are present return if @errors.present? + @tag.update(tag_params) if @tag.valid? @tag.update(realizations: realization_params) @@ -144,10 +144,10 @@ def search fulltext search_params[:title] end course_ids = if search_params[:all_courses] == '1' - [] - elsif search_params[:course_ids] != [''] - search_params[:course_ids] - end + [] + elsif search_params[:course_ids] != [''] + search_params[:course_ids] + end search.build do with(:course_ids, course_ids) paginate page: params[:page], per_page: per_page @@ -170,10 +170,13 @@ def postprocess @tags_hash.each do |t, section_data| tag = Tag.find_by_id(t) next unless tag + section_data.each do |s, v| next if v.to_i == 0 + section = Section.find(s) next unless section + if !tag.in?(section.tags) section.tags << tag end @@ -195,175 +198,176 @@ def render_tag_title private - def set_tag - @tag = Tag.find_by_id(params[:id]) - return if @tag.present? - redirect_to :root, alert: I18n.t('controllers.no_tag') - end + def set_tag + @tag = Tag.find_by_id(params[:id]) + return if @tag.present? - # set up cytoscape graph data for neighbourhood subgraph of @tag, - # using only neighbourhood tags that are allowd by the user's - # profile settings, depending on the parameters (selection/depth) that were - # specified by the user) - def set_related_tags_for_user - @depth = 2 - depth_param = params[:depth].to_i - @depth = depth_param if depth_param.in?([1, 2]) - overrule_subscription_type = false - selection = params[:selection].to_i - if selection.in?([1, 2, 3]) - overrule_subscription_type = selection - end - @selection_type = if overrule_subscription_type - selection - else - current_user.subscription_type - end - user_tags = current_user.visible_tags(overrule_subscription_type: overrule_subscription_type) - @related_tags = @tag.related_tags & user_tags - @tags_in_neighbourhood = if @depth == 2 - Tag.related_tags(@related_tags) & user_tags - else - [] - end - @tags = [@tag] + @related_tags + @tags_in_neighbourhood - @graph_elements = Tag.to_cytoscape(@tags, @tag, - highlight_related_tags: @depth == 2) - end + redirect_to :root, alert: I18n.t('controllers.no_tag') + end - # set up cytoscape graph data for neighbourhood subgraph of @tag, - def set_related_tags - related_tags = @tag.related_tags - tags_in_neighbourhood = Tag.related_tags(related_tags) - @graph_elements = Tag.to_cytoscape([@tag] + related_tags + - tags_in_neighbourhood, @tag) - end + # set up cytoscape graph data for neighbourhood subgraph of @tag, + # using only neighbourhood tags that are allowd by the user's + # profile settings, depending on the parameters (selection/depth) that were + # specified by the user) + def set_related_tags_for_user + @depth = 2 + depth_param = params[:depth].to_i + @depth = depth_param if depth_param.in?([1, 2]) + overrule_subscription_type = false + selection = params[:selection].to_i + if selection.in?([1, 2, 3]) + overrule_subscription_type = selection + end + @selection_type = if overrule_subscription_type + selection + else + current_user.subscription_type + end + user_tags = current_user.visible_tags(overrule_subscription_type: overrule_subscription_type) + @related_tags = @tag.related_tags & user_tags + @tags_in_neighbourhood = if @depth == 2 + Tag.related_tags(@related_tags) & user_tags + else + [] + end + @tags = [@tag] + @related_tags + @tags_in_neighbourhood + @graph_elements = Tag.to_cytoscape(@tags, @tag, + highlight_related_tags: @depth == 2) + end - def set_up_tag - @tag = Tag.new - set_notions - related_tag = Tag.find_by_id(params[:related_tag]) - @tag.related_tags << related_tag if related_tag.present? - end + # set up cytoscape graph data for neighbourhood subgraph of @tag, + def set_related_tags + related_tags = @tag.related_tags + tags_in_neighbourhood = Tag.related_tags(related_tags) + @graph_elements = Tag.to_cytoscape([@tag] + related_tags + + tags_in_neighbourhood, @tag) + end - def add_course - course = Course.find_by_id(params[:course]) - @tag.courses << course if course.present? - end + def set_up_tag + @tag = Tag.new + set_notions + related_tag = Tag.find_by_id(params[:related_tag]) + @tag.related_tags << related_tag if related_tag.present? + end - def add_section - section = Section.find_by_id(params[:section]) - if section - @tag.sections << section - I18n.locale = section.lecture.locale || current_user.locale + def add_course + course = Course.find_by_id(params[:course]) + @tag.courses << course if course.present? end - end - def add_medium - medium = Medium.find_by_id(params[:medium]) - if medium + def add_section + section = Section.find_by_id(params[:section]) + if section + @tag.sections << section + I18n.locale = section.lecture.locale || current_user.locale + end + end + + def add_medium + medium = Medium.find_by_id(params[:medium]) + if medium I18n.locale = medium.locale_with_inheritance || current_user.locale @tag.media << medium + end end - end - def add_lesson - lesson = Lesson.find_by_id(params[:lesson]) - if lesson - @tag.lessons << lesson - I18n.locale = lesson.lecture.locale || current_user.locale + def add_lesson + lesson = Lesson.find_by_id(params[:lesson]) + if lesson + @tag.lessons << lesson + I18n.locale = lesson.lecture.locale || current_user.locale + end end - end - def add_talk - talk = Talk.find_by_id(params[:talk]) - if talk - @tag.talks << talk - I18n.locale = talk.lecture.locale || current_user.locale + def add_talk + talk = Talk.find_by_id(params[:talk]) + if talk + @tag.talks << talk + I18n.locale = talk.lecture.locale || current_user.locale + end end - end - def check_for_consent - redirect_to consent_profile_path unless current_user.consents - end + def check_for_consent + redirect_to consent_profile_path unless current_user.consents + end - def tag_params - params.require(:tag).permit(related_tag_ids: [], - notions_attributes: [:title, :locale, :id, - :_destroy], - aliases_attributes: [:title, :locale, :id, - :_destroy], - course_ids: [], - section_ids: [], - lesson_ids: [], - talk_ids: [], - media_ids: []) - end + def tag_params + params.require(:tag).permit(related_tag_ids: [], + notions_attributes: [:title, :locale, :id, + :_destroy], + aliases_attributes: [:title, :locale, :id, + :_destroy], + course_ids: [], + section_ids: [], + lesson_ids: [], + talk_ids: [], + media_ids: []) + end - def realization_params - (params.require(:tag).permit(realizations: [])[:realizations] - ['']) - .map { |r| r.split('-') } - .map { |x| [x.first, x.second.to_i] } - end + def realization_params + (params.require(:tag).permit(realizations: [])[:realizations] - ['']) + .map { |r| r.split('-') } + .map { |x| [x.first, x.second.to_i] } + end - def check_permissions - @errors = {} - return if current_user.admin? - # of current user is not an admin, he can add/remove courses only - # as course editor with inheritance/course_editor - permission_errors - end + def check_permissions + @errors = {} + return if current_user.admin? - def permission_errors - errors = [] - unless removed_courses.all? { |c| c.removable_by?(current_user) } - errors.push(error_hash['remove_course']) - end - unless added_courses.all? { |c| c.addable_by?(current_user) } - errors.push(error_hash['add_course']) + # of current user is not an admin, he can add/remove courses only + # as course editor with inheritance/course_editor + permission_errors end - @errors[:courses] = errors if errors.present? - end - def check_creation_permission - @modal = (params[:tag][:modal] == 'true') - @tag = Tag.new - check_permissions - end + def permission_errors + errors = [] + unless removed_courses.all? { |c| c.removable_by?(current_user) } + errors.push(error_hash['remove_course']) + end + unless added_courses.all? { |c| c.addable_by?(current_user) } + errors.push(error_hash['add_course']) + end + @errors[:courses] = errors if errors.present? + end - def removed_courses - @tag.courses - Course.where(id: tag_params[:course_ids]) - end + def check_creation_permission + @modal = (params[:tag][:modal] == 'true') + @tag = Tag.new + check_permissions + end - def added_courses - Course.where(id: tag_params[:course_ids]) - @tag.courses - end + def removed_courses + @tag.courses - Course.where(id: tag_params[:course_ids]) + end - def set_notions - @tag.notions.new(locale: I18n.locale) - (I18n.available_locales - [I18n.locale]).each do |l| - @tag.notions.new(locale: l) + def added_courses + Course.where(id: tag_params[:course_ids]) - @tag.courses end - end - def locale - locale = if params[:from] == 'course' - @tag.courses&.first&.locale - elsif params[:from] == 'medium' - @tag.media&.first&.locale_with_inheritance - elsif params[:from] == 'section' - @tag.sections&.first&.lecture&.locale_with_inheritance - end - locale || current_user.locale - end + def set_notions + @tag.notions.new(locale: I18n.locale) + (I18n.available_locales - [I18n.locale]).each do |l| + @tag.notions.new(locale: l) + end + end - def error_hash - { 'remove_course' => I18n.t('controllers.no_removal_rights'), - 'add_course' => I18n.t('controllers.no_adding_rights') } - end + def locale + locale = if params[:from] == 'course' + @tag.courses&.first&.locale + elsif params[:from] == 'medium' + @tag.media&.first&.locale_with_inheritance + elsif params[:from] == 'section' + @tag.sections&.first&.lecture&.locale_with_inheritance + end + locale || current_user.locale + end - def search_params - params.require(:search).permit(:title, :all_courses, :per, course_ids: []) - end + def error_hash + { 'remove_course' => I18n.t('controllers.no_removal_rights'), + 'add_course' => I18n.t('controllers.no_adding_rights') } + end + def search_params + params.require(:search).permit(:title, :all_courses, :per, course_ids: []) + end end diff --git a/app/controllers/terms_controller.rb b/app/controllers/terms_controller.rb index 6717853d3..af473afe0 100644 --- a/app/controllers/terms_controller.rb +++ b/app/controllers/terms_controller.rb @@ -66,18 +66,19 @@ def set_active private - def set_term - @id = params[:id] - @term = Term.find_by_id(@id) - return if @term - redirect_to terms_path, alert: I18n.t('controllers.no_term') - end + def set_term + @id = params[:id] + @term = Term.find_by_id(@id) + return if @term - def term_params - params.require(:term).permit(:year, :season) - end + redirect_to terms_path, alert: I18n.t('controllers.no_term') + end - def active_term_params - params.permit(:active_term) - end + def term_params + params.require(:term).permit(:year, :season) + end + + def active_term_params + params.permit(:active_term) + end end diff --git a/app/controllers/tutorials_controller.rb b/app/controllers/tutorials_controller.rb index 9bbcfc12a..f849a708a 100644 --- a/app/controllers/tutorials_controller.rb +++ b/app/controllers/tutorials_controller.rb @@ -26,7 +26,7 @@ def index authorize! :index, Tutorial.new, @lecture @assignments = @lecture.assignments.order('deadline DESC') @assignment = Assignment.find_by_id(params[:assignment]) || - @assignments&.first + @assignments&.first if current_user.editor_or_teacher_in?(@lecture) @tutorials = @lecture.tutorials else @@ -41,7 +41,7 @@ def overview authorize! :overview, Tutorial.new, @lecture @assignments = @lecture.assignments.order('deadline DESC') @assignment = Assignment.find_by_id(params[:assignment]) || - @assignments&.first + @assignments&.first @tutorials = @lecture.tutorials end @@ -117,81 +117,88 @@ def validate_certificate def export_teams respond_to do |format| format.html { head :ok } - format.csv { send_data @tutorial.teams_to_csv(@assignment), - filename: "#{@tutorial.title}-#{@assignment.title}.csv" } + format.csv { + send_data @tutorial.teams_to_csv(@assignment), + filename: "#{@tutorial.title}-#{@assignment.title}.csv" + } end end private - def set_tutorial - @tutorial = Tutorial.find_by_id(params[:id]) - @lecture = @tutorial&.lecture - set_tutorial_locale and return if @tutorial - redirect_to :root, alert: I18n.t('controllers.no_tutorial') - end + def set_tutorial + @tutorial = Tutorial.find_by_id(params[:id]) + @lecture = @tutorial&.lecture + set_tutorial_locale and return if @tutorial - def set_assignment - @assignment = Assignment.find_by_id(params[:ass_id]) - return if @assignment - redirect_to :root, alert: I18n.t('controllers.no_assignment') - end + redirect_to :root, alert: I18n.t('controllers.no_tutorial') + end - def set_lecture - @lecture = Lecture.find_by_id(params[:id]) - set_tutorial_locale and return if @lecture - redirect_to :root, alert: I18n.t('controllers.no_lecture') - end + def set_assignment + @assignment = Assignment.find_by_id(params[:ass_id]) + return if @assignment - def set_lecture_from_form - @lecture = Lecture.find_by_id(tutorial_params[:lecture_id]) - return if @lecture - redirect_to :root, alert: I18n.t('controllers.no_lecture') - end + redirect_to :root, alert: I18n.t('controllers.no_assignment') + end + + def set_lecture + @lecture = Lecture.find_by_id(params[:id]) + set_tutorial_locale and return if @lecture - def set_tutorial_locale - I18n.locale = @lecture&.locale_with_inheritance || current_user.locale || + redirect_to :root, alert: I18n.t('controllers.no_lecture') + end + + def set_lecture_from_form + @lecture = Lecture.find_by_id(tutorial_params[:lecture_id]) + return if @lecture + + redirect_to :root, alert: I18n.t('controllers.no_lecture') + end + + def set_tutorial_locale + I18n.locale = @lecture&.locale_with_inheritance || current_user.locale || I18n.default_locale - end + end - def can_view_index - return if current_user.in?(@lecture.tutors) || current_user.editor_or_teacher_in?(@lecture) - redirect_to :root, alert: I18n.t('controllers.no_tutor_in_this_lecture') - end + def can_view_index + return if current_user.in?(@lecture.tutors) || current_user.editor_or_teacher_in?(@lecture) - def tutorial_params - params.require(:tutorial).permit(:title, :lecture_id, tutor_ids: []) - end + redirect_to :root, alert: I18n.t('controllers.no_tutor_in_this_lecture') + end - def bulk_params - params.permit(:package) - end + def tutorial_params + params.require(:tutorial).permit(:title, :lecture_id, tutor_ids: []) + end - def bulk_download(zipped, end_of_file='') - if zipped.is_a?(StringIO) - send_data zipped.read, - filename: @assignment.title + '@' + @tutorial.title + end_of_file + '.zip', - type: 'application/zip', - disposition: 'attachment' - else - flash[:alert] = I18n.t('controllers.tutorials.bulk_download_failed', - message: zipped) - redirect_to lecture_tutorials_path(@tutorial.lecture, - params: - { assignment: @assignment.id, - tutorial: @tutorial.id }) + def bulk_params + params.permit(:package) end - end - def send_correction_upload_emails - @report[:successful_saves]&.each do |submission| - submission.users.email_for_correction_upload.each do |u| - NotificationMailer.with(recipient: u, - locale: u.locale, - submission: submission, - tutor: current_user) - .correction_upload_email.deliver_later + def bulk_download(zipped, end_of_file = '') + if zipped.is_a?(StringIO) + send_data zipped.read, + filename: @assignment.title + '@' + @tutorial.title + end_of_file + '.zip', + type: 'application/zip', + disposition: 'attachment' + else + flash[:alert] = I18n.t('controllers.tutorials.bulk_download_failed', + message: zipped) + redirect_to lecture_tutorials_path(@tutorial.lecture, + params: + { assignment: @assignment.id, + tutorial: @tutorial.id }) end end - end -end \ No newline at end of file + + def send_correction_upload_emails + @report[:successful_saves]&.each do |submission| + submission.users.email_for_correction_upload.each do |u| + NotificationMailer.with(recipient: u, + locale: u.locale, + submission: submission, + tutor: current_user) + .correction_upload_email.deliver_later + end + end + end +end diff --git a/app/controllers/users_controller.rb b/app/controllers/users_controller.rb index 405e20bf7..0ed0ae4c2 100644 --- a/app/controllers/users_controller.rb +++ b/app/controllers/users_controller.rb @@ -39,6 +39,7 @@ def elevate @user = User.find(elevate_params[:id]) admin = elevate_params[:admin] == '1' return unless admin + # enforce a name if @user.name.blank? name = @user.email.split('@')[0] @@ -90,23 +91,25 @@ def delete_account private - def elevate_params - params.require(:generic_user).permit(:id, :admin, :editor, :teacher, :name) - end + def elevate_params + params.require(:generic_user).permit(:id, :admin, :editor, :teacher, + :name) + end - def user_params - params.require(:user).permit(:name, :email, :homepage, - :current_lecture_id,:image) - end + def user_params + params.require(:user).permit(:name, :email, :homepage, + :current_lecture_id, :image) + end - def set_user - @user = User.find_by_id(params[:id]) - return unless @user.nil? - redirect_to :root, alert: I18n.t('controllers.no_medium') - end + def set_user + @user = User.find_by_id(params[:id]) + return unless @user.nil? - def set_elevated_users - @elevated_users = User.where(admin: true).or(User.proper_editors) - .or(User.teachers) - end -end \ No newline at end of file + redirect_to :root, alert: I18n.t('controllers.no_medium') + end + + def set_elevated_users + @elevated_users = User.where(admin: true).or(User.proper_editors) + .or(User.teachers) + end +end diff --git a/app/controllers/vertices_controller.rb b/app/controllers/vertices_controller.rb index 776389313..3f3e027d9 100644 --- a/app/controllers/vertices_controller.rb +++ b/app/controllers/vertices_controller.rb @@ -50,51 +50,54 @@ def destroy private - def set_values - @quiz_id = params[:quiz_id] - @quiz = Quiz.find_by_id(@quiz_id) - @params_v = params[:vertex] - end + def set_values + @quiz_id = params[:quiz_id] + @quiz = Quiz.find_by_id(@quiz_id) + @params_v = params[:vertex] + end - def set_update_vertex_params - @vertex_id = @params_v[:vertex_id].to_i - @branching = {} - set_branching_hash - set_hide_array - end + def set_update_vertex_params + @vertex_id = @params_v[:vertex_id].to_i + @branching = {} + set_branching_hash + set_hide_array + end - def set_create_vertex_params - @sort = @params_v[:sort] - if @sort == 'import' - @quizzables = Medium.where(id: @params_v[:quizzable_ids], - type: ['Question', 'Remark']) - @success = @quizzables.any? - else - quizzable = @sort.constantize.create_prefilled(@params_v[:label], - @quiz.teachable, - @quiz.editors) - @success = quizzable.valid? - @quizzables = [quizzable] + def set_create_vertex_params + @sort = @params_v[:sort] + if @sort == 'import' + @quizzables = Medium.where(id: @params_v[:quizzable_ids], + type: ['Question', 'Remark']) + @success = @quizzables.any? + else + quizzable = @sort.constantize.create_prefilled(@params_v[:label], + @quiz.teachable, + @quiz.editors) + @success = quizzable.valid? + @quizzables = [quizzable] + end end - end - def set_branching_hash - @branching = {} - @params_v.keys.select { |k| k.start_with?('branching-') }.each do |k| - next if @params_v[k].to_i == 0 - @branching[k.remove('branching-').to_h] = [@vertex_id, @params_v[k].to_i] + def set_branching_hash + @branching = {} + @params_v.keys.select { |k| k.start_with?('branching-') }.each do |k| + next if @params_v[k].to_i == 0 + + @branching[k.remove('branching-').to_h] = + [@vertex_id, @params_v[k].to_i] + end end - end - def set_hide_array - @hide = @params_v.keys.select { |k| k.start_with?('hide-') } - .select { |h| @params_v[h] == '1' } - .map { |h| h.remove('hide-').to_h } - end + def set_hide_array + @hide = @params_v.keys.select { |k| k.start_with?('hide-') } + .select { |h| @params_v[h] == '1' } + .map { |h| h.remove('hide-').to_h } + end - def check_permission - return if current_user.admin - return if current_user.can_edit?(@quiz) - redirect_to :root, alert: I18n.t('controllers.unauthorized') - end + def check_permission + return if current_user.admin + return if current_user.can_edit?(@quiz) + + redirect_to :root, alert: I18n.t('controllers.unauthorized') + end end diff --git a/app/controllers/watchlist_entries_controller.rb b/app/controllers/watchlist_entries_controller.rb index f41c37de8..106e8644e 100644 --- a/app/controllers/watchlist_entries_controller.rb +++ b/app/controllers/watchlist_entries_controller.rb @@ -1,6 +1,5 @@ # WatchlistEntriesController class WatchlistEntriesController < ApplicationController - def current_ability @current_ability ||= WatchlistEntryAbility.new(current_user) end diff --git a/app/controllers/watchlists_controller.rb b/app/controllers/watchlists_controller.rb index 77bff44a2..fc616dbf9 100644 --- a/app/controllers/watchlists_controller.rb +++ b/app/controllers/watchlists_controller.rb @@ -71,6 +71,7 @@ def show authorize! :show, @watchlist @watchlists = current_user.watchlists return if @watchlist.watchlist_entries.empty? + @watchlist_entries = paginated_results @media = @watchlist_entries.pluck(:medium_id) end @@ -81,7 +82,6 @@ def add_medium @medium = Medium.find_by_id(params[:medium_id]) end - def update_order entries = params[:order].map { |id| WatchlistEntry.find_by_id(id) } authorize! :update_order, @watchlist, entries @@ -104,9 +104,11 @@ def change_visibility end private + def set_watchlist @watchlist = Watchlist.find_by_id(params[:id]) return if @watchlist.present? + redirect_to :root, alert: I18n.t('controllers.no_watchlist') end @@ -130,6 +132,7 @@ def paginated_results def filter_results filter_results = @watchlist.watchlist_entries return filter_results unless params[:reverse] + filter_results.reverse end @@ -140,5 +143,4 @@ def update_params def create_params params.require(:watchlist).permit(:name, :description, :medium_id) end - -end \ No newline at end of file +end diff --git a/app/helpers/announcements_helper.rb b/app/helpers/announcements_helper.rb index 28203725c..9994c564b 100644 --- a/app/helpers/announcements_helper.rb +++ b/app/helpers/announcements_helper.rb @@ -6,6 +6,7 @@ def announcement_notification_item_header(announcement) unless announcement.lecture.present? return t('notifications.mampf_announcement') end + t('notifications.lecture_announcement', title: announcement.lecture.title_for_viewers) end @@ -14,6 +15,7 @@ def announcement_notification_item_header(announcement) def news_card_color(announcement) return '' unless user_signed_in? return 'bg-post-it-blue' if announcement.active?(current_user) + '' end diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index c88cfef2e..23fb3040c 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -1,10 +1,10 @@ # ApplicationHelper module module ApplicationHelper - - #returns the path that is associated to the MaMpf brand in the navbar + # returns the path that is associated to the MaMpf brand in the navbar def home_path return start_path if user_signed_in? - root_path(params: { locale: I18n.locale}) + + root_path(params: { locale: I18n.locale }) end # get current lecture from session object @@ -30,6 +30,7 @@ def download_host def full_title(page_title = '') return page_title if action_name == 'play' && controller_name == 'media' return 'Quiz' if action_name == 'take' && controller_name == 'quizzes' + base_title = 'MaMpf' if user_signed_in? && current_user.notifications.any? base_title += " (#{current_user.notifications.size})" @@ -109,7 +110,7 @@ def media_names # Selects all media associated to lectures and lessons from a given list # of media def lecture_media(media) - media.where(teachable_type: ['Lecture', 'Lesson'] ) + media.where(teachable_type: ['Lecture', 'Lesson']) end # Selects all media associated to courses from a given list of media @@ -121,10 +122,10 @@ def course_media(media) # the given media are associated to. def lecture_course_teachables(media) teachables = media.pluck(:teachable_type, :teachable_id).uniq - course_ids = teachables.select { |t| t.first == 'Course'}.map(&:second) - lecture_ids = teachables.select { |t| t.first == 'Lecture'}.map(&:second) - lesson_ids = teachables.select { |t| t.first == 'Lesson'}.map(&:second) - talk_ids = teachables.select { |t| t.first == 'Talk'}.map(&:second) + course_ids = teachables.select { |t| t.first == 'Course' }.map(&:second) + lecture_ids = teachables.select { |t| t.first == 'Lecture' }.map(&:second) + lesson_ids = teachables.select { |t| t.first == 'Lesson' }.map(&:second) + talk_ids = teachables.select { |t| t.first == 'Talk' }.map(&:second) lecture_ids += Lesson.where(id: lesson_ids).pluck(:lecture_id).uniq lecture_ids += Talk.where(id: talk_ids).pluck(:lecture_id).uniq Course.where(id: course_ids) + Lecture.where(id: lecture_ids.uniq) @@ -139,8 +140,8 @@ def relevant_media(teachable, media, limit) result = [] if teachable.class == Course return media.where(teachable: teachable).order(:created_at) - .reverse_order - .first(limit) + .reverse_order + .first(limit) end media_ids = (teachable.media_with_inheritance.pluck(:id) & media.pluck(:id)) Medium.where(id: media_ids).order(:created_at).reverse_order.first(limit) @@ -152,6 +153,7 @@ def split_list(list, pieces = 4) groups = list.in_groups_of(group_size) diff = groups.count - pieces return groups if diff <= 0 + tail = groups.pop(diff).first(diff).flatten groups.last.concat(tail) groups @@ -160,6 +162,7 @@ def split_list(list, pieces = 4) # returns true for 'media#enrich' action def enrich?(controller, action) return true if controller == 'media' && action == 'enrich' + false end @@ -168,6 +171,7 @@ def enrich?(controller, action) def shorten(title, max_letters) return '' unless title.present? return title unless title.length > max_letters + title[0, max_letters - 3] + '...' end @@ -176,7 +180,8 @@ def shorten(title, max_letters) def grouped_teachable_list list = [] Course.all.each do |c| - lectures = [[c.short_title + ' (' + t('basics.all') + ')', 'Course-' + c.id.to_s]] + lectures = [[c.short_title + ' (' + t('basics.all') + ')', + 'Course-' + c.id.to_s]] c.lectures.includes(:term).each do |l| lectures.push [l.short_title_release, 'Lecture-' + l.id.to_s] end @@ -206,6 +211,7 @@ def grouped_teachable_list_alternative # can edit all lectures associated to the course. def edit_or_show_lecture_path(lecture) return edit_lecture_path(lecture) if current_user.can_edit?(lecture) + lecture_path(lecture) end @@ -217,6 +223,7 @@ def edit_or_inspect_medium_path(medium) medium.editors_with_inheritance.include?(current_user) return edit_medium_path(medium) end + inspect_medium_path(medium) end @@ -224,10 +231,11 @@ def edit_or_inspect_medium_path(medium) # anything older than today or yesterday gets reduced to the day.month.year # yesterday's/today's dates are return as 'gestern/heute' plus hour:mins def human_readable_date(date) - return t('today')+ ', ' + date.strftime('%H:%M') if date.to_date == Date.today + return t('today') + ', ' + date.strftime('%H:%M') if date.to_date == Date.today if date.to_date == Date.yesterday return t('yesterday') + ', ' + date.strftime('%H:%M') end + I18n.localize date, format: :concise end @@ -266,12 +274,12 @@ def hide_as_class(value) def helpdesk(text, html) tag.i class: 'far fa-question-circle helpdesk ml-2', - tabindex: -1, - data: { toggle: 'popover', - trigger: 'focus', - content: text, - html: html }, - title: t('info') + tabindex: -1, + data: { toggle: 'popover', + trigger: 'focus', + content: text, + html: html }, + title: t('info') end def realization_path(realization) @@ -291,7 +299,7 @@ def get_announcements # Navbar items styling based on which page we are on # https://gist.github.com/mynameispj/5692162 $active_css_class = "active-item" - + def get_class_for_project(project) return request.params['project'] == project ? $active_css_class : '' end @@ -310,9 +318,9 @@ def get_class_for_any_path(paths) def get_class_for_any_path_startswith(paths) if paths.any? { |path| request.path.starts_with?(path) } - return $active_css_class + return $active_css_class end + return '' end - end diff --git a/app/helpers/assignments_helper.rb b/app/helpers/assignments_helper.rb index d61d3c0aa..ce0369e2f 100644 --- a/app/helpers/assignments_helper.rb +++ b/app/helpers/assignments_helper.rb @@ -2,11 +2,13 @@ module AssignmentsHelper def cancel_editing_assignment_path(assignment) return cancel_edit_assignment_path(assignment) if assignment.persisted? + cancel_new_assignment_path(params: { lecture: assignment.lecture }) end def has_documents?(assignment) return false unless assignment.medium + assignment.medium.video || assignment.medium.manuscript || assignment.medium.geogebra || assignment.medium.external_reference_link.present? || @@ -15,6 +17,7 @@ def has_documents?(assignment) def file_button_text(assignment) return I18n.t('basics.file') unless assignment.accepted_file_type == '.pdf' + I18n.t('basics.files') end end diff --git a/app/helpers/chapters_helper.rb b/app/helpers/chapters_helper.rb index ac711d717..8d806f67b 100644 --- a/app/helpers/chapters_helper.rb +++ b/app/helpers/chapters_helper.rb @@ -4,4 +4,4 @@ def chapter_positions_for_select(chapter) [[t('basics.at_the_beginning'), 0]] + chapter.lecture.select_chapters - [[chapter.to_label, chapter.position]] end -end \ No newline at end of file +end diff --git a/app/helpers/clickers_helper.rb b/app/helpers/clickers_helper.rb index 58f2f2c72..cde787a1e 100644 --- a/app/helpers/clickers_helper.rb +++ b/app/helpers/clickers_helper.rb @@ -10,4 +10,4 @@ def generate_qr(text) base64_output = Base64.encode64(qrcode.to_png({ xdim: 8 })) "data:image/png;base64,#{base64_output}" end -end \ No newline at end of file +end diff --git a/app/helpers/courses_helper.rb b/app/helpers/courses_helper.rb index 34d9a028c..7b1758916 100644 --- a/app/helpers/courses_helper.rb +++ b/app/helpers/courses_helper.rb @@ -27,6 +27,7 @@ def course_link_or_text(course, user) unless user.admin || user.in?(course.editors) return course.title end + link_to(course.title, edit_course_path(course)) end @@ -40,5 +41,4 @@ def course_edit_icon(course) tag.i class: 'far fa-edit' end end - end diff --git a/app/helpers/items_helper.rb b/app/helpers/items_helper.rb index 2bb92a957..217a5c6cc 100644 --- a/app/helpers/items_helper.rb +++ b/app/helpers/items_helper.rb @@ -17,6 +17,7 @@ def select_script_items(lecture) def check_unless_hidden(item_id) return 'checked' unless Item.find_by_id(item_id)&.hidden + '' end diff --git a/app/helpers/lectures_helper.rb b/app/helpers/lectures_helper.rb index 3123e56c8..c64225e9a 100644 --- a/app/helpers/lectures_helper.rb +++ b/app/helpers/lectures_helper.rb @@ -41,24 +41,28 @@ def days_short # unpublished lecture get a different link color def lectures_color(lecture) return '' if lecture.published? + 'unpublished' end # hidden chapters get a different color def chapter_card_color(chapter) return 'bg-mdb-color-lighten-5' unless chapter.hidden + 'greyed_out bg-grey' end # hidden chapters get a different header color def chapter_header_color(chapter) return 'bg-mdb-color-lighten-2' unless chapter.hidden + '' end # hidden sections get a different color def section_color(section) return '' unless section.hidden + 'greyed_out' end @@ -67,36 +71,42 @@ def section_background_color(section) unless !section.chapter.hidden && section.hidden return 'bg-mdb-color-lighten-6' end + 'bg-grey' end def news_color(news_count) return '' unless news_count.positive? + 'text-primary' end def lecture_header_color(subscribed, lecture) return '' unless subscribed + result = 'text-light ' result += if lecture.term - 'bg-mdb-color-lighten-1' - else - 'bg-info' - end + 'bg-mdb-color-lighten-1' + else + 'bg-info' + end end def circle_icon(subscribed) return 'fas fa-check-circle' if subscribed + 'far fa-circle' end def lecture_border(lecture) return '' if lecture.published? + 'border-danger' end def lecture_access_icon(lecture) return lecture_edit_icon if current_user.can_edit?(lecture) + lecture_view_icon end diff --git a/app/helpers/lessons_helper.rb b/app/helpers/lessons_helper.rb index 0f2e016d8..0653e9207 100644 --- a/app/helpers/lessons_helper.rb +++ b/app/helpers/lessons_helper.rb @@ -10,6 +10,7 @@ def lesson_tag_selection(lesson) def edit_or_show_lesson_path(lesson) return edit_lesson_path(lesson) if current_user.can_edit?(lesson.lecture) + lesson_path(lesson) end end diff --git a/app/helpers/media_helper.rb b/app/helpers/media_helper.rb index 317c9eed6..a95385e85 100644 --- a/app/helpers/media_helper.rb +++ b/app/helpers/media_helper.rb @@ -32,6 +32,7 @@ def inspect_or_edit_medium_path(medium, inspection) # create text for notification about new medium in notification dropdown menu def medium_notification_item_header(medium) return unless medium.proper? + t('notifications.new_medium_in') + medium.scoped_teachable_title end @@ -45,6 +46,7 @@ def medium_notification_card_header(medium) if teachable.media_scope.class.to_s == 'Course' return teachable.media_scope.title_for_viewers end + link_to(teachable.media_scope.title_for_viewers, medium.teachable.media_scope.path(current_user), class: 'text-dark') @@ -63,14 +65,15 @@ def section_selection(medium) def preselected_sections(medium) return [] unless medium.teachable.class.to_s == 'Lesson' + medium.teachable.sections.map(&:id) end - def textcolor(medium) return '' if medium.visible? return 'locked' if medium.locked? return 'scheduled_release' if medium.publisher.present? + 'unpublished' end @@ -78,6 +81,7 @@ def infotainment(medium) return 'nichts' unless medium.video || medium.manuscript return 'ein Video' unless medium.manuscript return 'ein Manuskript' unless medium.video + 'ein Video und ein Manuskript' end @@ -85,16 +89,19 @@ def level_to_word(medium) return t('basics.not_set') unless medium.level.present? return t('basics.level_easy') if medium.level == 0 return t('basics.level_medium') if medium.level == 1 + t('basics.level_hard') end def independent_to_word(medium) return t('basics.no_lc') unless medium.independent + t('basics.yes_lc') end def medium_border(medium) return if medium.published? && !medium.locked? + 'border-danger' end @@ -103,17 +110,19 @@ def media_sorts_select(purpose) return Medium.select_question if purpose == 'clicker' return add_prompt(Medium.select_importables) if purpose == 'import' return add_prompt(Medium.select_generic) if !current_user.admin? + add_prompt(Medium.select_sorts) end def sort_preselect(purpose) return '' unless purpose == 'quiz' + 'Question' end def related_media_hash(references, media) media_list = references.map { |r| [r.medium, r.manuscript_link] } + - media.zip(Array.new(media.size)) + media.zip(Array.new(media.size)) hash = {} Medium.sort_enum.each do |s| media_in_s = media_list.select { |m| m.first.sort == s } @@ -124,6 +133,7 @@ def related_media_hash(references, media) def release_date_info(medium) return unless medium.publisher.present? + t('admin.medium.scheduled_for_release_short', release_date: I18n.l(medium.publisher&.release_date, format: :long, @@ -132,6 +142,7 @@ def release_date_info(medium) def edit_or_show_medium_path(medium) return edit_medium_path(medium) if current_user.can_edit?(medium) + medium_path(medium) end diff --git a/app/helpers/notifications_helper.rb b/app/helpers/notifications_helper.rb index 8096b8447..74ff9883c 100644 --- a/app/helpers/notifications_helper.rb +++ b/app/helpers/notifications_helper.rb @@ -7,6 +7,7 @@ def notification_menu_item_header(notification) return medium_notification_item_header(notifiable) if notification.medium? return course_notification_item_header(notifiable) if notification.course? return lecture_notification_item_header(notifiable) if notification.lecture? + announcement_notification_item_header(notifiable) end @@ -18,6 +19,7 @@ def notification_menu_item_details(notification) if notification.lecture? return lecture_notification_item_details(notifiable) end + '' end @@ -26,6 +28,7 @@ def notification_color(notification) return 'bg-post-it-blue' if notification.generic_announcement? return 'bg-post-it-red' if notification.announcement? return 'bg-post-it-orange' if notification.course? || notification.lecture? + 'bg-post-it-yellow' end @@ -33,14 +36,14 @@ def notification_color(notification) def notification_header(notification) notifiable = notification.notifiable text = if notification.medium? - medium_notification_card_header(notifiable) - elsif notification.course? || notification.lecture? - t('notifications.course_selection') - elsif notification.lecture_announcement? - announcement_notification_card_header(notifiable) - else - link_to t('mampf_news.title'), news_path, class: 'text-dark' - end + medium_notification_card_header(notifiable) + elsif notification.course? || notification.lecture? + t('notifications.course_selection') + elsif notification.lecture_announcement? + announcement_notification_card_header(notifiable) + else + link_to t('mampf_news.title'), news_path, class: 'text-dark' + end text.html_safe end @@ -48,14 +51,14 @@ def notification_header(notification) def notification_text(notification) notifiable = notification.notifiable text = if notification.medium? - t('notifications.new_medium') - elsif notification.course? - course_notification_card_text(notifiable) - elsif notification.lecture? - lecture_notification_card_text(notifiable) - else - t('notifications.new_announcement') - end + t('notifications.new_medium') + elsif notification.course? + course_notification_card_text(notifiable) + elsif notification.lecture? + lecture_notification_card_text(notifiable) + else + t('notifications.new_announcement') + end text.html_safe end @@ -63,21 +66,23 @@ def notification_text(notification) def notification_link(notification) notifiable = notification.notifiable return '' unless notifiable + text = if notification.medium? - medium_notification_card_link(notifiable) - elsif notification.course? - course_notification_card_link - elsif notification.lecture? - lecture_notification_card_link - else - notifiable.details - end + medium_notification_card_link(notifiable) + elsif notification.course? + course_notification_card_link + elsif notification.lecture? + lecture_notification_card_link + else + notifiable.details + end text.html_safe end def items_card_size(small, comments_below) return '30vh' if comments_below return '60vh' if small + '70vh' end end diff --git a/app/helpers/referrals_helper.rb b/app/helpers/referrals_helper.rb index 14d80fda3..fe8eef939 100644 --- a/app/helpers/referrals_helper.rb +++ b/app/helpers/referrals_helper.rb @@ -9,16 +9,19 @@ def teachable_selector(referral) return referral.medium.teachable&.media_scope&.selector_value end return 'external-0' if referral.item.sort == 'link' + referral.item.medium.teachable&.media_scope&.selector_value end def show_link(referral) return true if referral.item.present? && referral.item.sort == 'link' + false end def show_explanation(referral) return false if referral.item.nil? + true end @@ -28,15 +31,17 @@ def show_explanation(referral) def item_status_color(referral) return '' if referral.item.sort == 'link' if !referral.item_published? || referral.item_locked? || - referral.item.quarantine + referral.item.quarantine return 'bg-post-it-pink' end + '' end def item_status_color_value(referral) return 'white' if referral.item.sort == 'link' return '#fad1df' if !referral.item_published? || referral.item_locked? + 'white' end end diff --git a/app/helpers/search_helper.rb b/app/helpers/search_helper.rb index 91b5eea6c..e1e5d90c4 100644 --- a/app/helpers/search_helper.rb +++ b/app/helpers/search_helper.rb @@ -10,11 +10,13 @@ def plural_n(tags, filtered_tags) def hits_per_page(results_as_list) return [[10, 10], [20, 20], [50, 50]] if results_as_list - [[3,3],[4,4],[6,6], [12,12]] + + [[3, 3], [4, 4], [6, 6], [12, 12]] end def default_hits_per_page(results_as_list) return 20 if results_as_list + 6 end end diff --git a/app/helpers/submissions_helper.rb b/app/helpers/submissions_helper.rb index 78b89e8f0..b44505b31 100644 --- a/app/helpers/submissions_helper.rb +++ b/app/helpers/submissions_helper.rb @@ -2,6 +2,7 @@ module SubmissionsHelper def cancel_editing_submission_path(submission) return cancel_edit_submission_path(submission) if submission.persisted? + cancel_new_submission_path(params: { assignment_id: submission.assignment.id }) end @@ -14,49 +15,58 @@ def partner_preselection(user, lecture) end def admissible_invitee_selection(user, submission, lecture) - submission.admissible_invitees(user).map { |u| [u.tutorial_name, u.id] } + submission.admissible_invitees(user).map { |u| [u.tutorial_name, u.id] } end def probable_invitee_ids(user, submission, lecture) - partner_preselection(user, lecture) - - (submission.users + submission.invited_users).map(&:id) + partner_preselection(user, lecture) - + (submission.users + submission.invited_users).map(&:id) end def invitations_possible?(submission, user) - return false if submission.admissible_invitees(user).empty? - return true unless submission.assignment.lecture.submission_max_team_size - submission.users.size < - submission.assignment.lecture.submission_max_team_size + return false if submission.admissible_invitees(user).empty? + return true unless submission.assignment.lecture.submission_max_team_size + + submission.users.size < + submission.assignment.lecture.submission_max_team_size end def submission_color(submission, assignment) - if assignment.active? - return 'bg-submission-green' if submission&.manuscript - return 'bg-submission-yellow' if submission - return 'bg-submission-red' - else - return 'bg-submission-darker-green' if submission&.correction + if assignment.active? + return 'bg-submission-green' if submission&.manuscript + return 'bg-submission-yellow' if submission + + return 'bg-submission-red' + else + return 'bg-submission-darker-green' if submission&.correction + if submission&.manuscript && submission.too_late? return 'bg-submission-orange' if submission.accepted.nil? return 'bg-submission-green' if submission.accepted + return 'bg-submission-red' end - return 'bg-submission-green' if submission&.manuscript - return 'bg-submission-red' - end + return 'bg-submission-green' if submission&.manuscript + + return 'bg-submission-red' + end end def submission_status_icon(submission, assignment) if assignment.active? return 'far fa-smile' if submission&.manuscript + return 'fas fa-exclamation-triangle' else return 'far fa-smile' if submission&.correction + if submission&.manuscript && submission.too_late? return 'fas fa-hourglass-start' if submission.accepted + return 'fas fa-exclamation-triangle' end return 'fas fa-hourglass-start' if submission&.manuscript + return 'fas fa-exclamation-triangle' end end @@ -65,23 +75,27 @@ def submission_status_text(submission, assignment) if assignment.active? return t('submission.okay') if submission&.manuscript return t('submission.no_file') if submission + return t('submission.nothing') else return t('submission.with_correction') if submission&.correction + if submission&.manuscript && submission.too_late? return t('submission.too_late') if submission.accepted.nil? return t('submission.too_late_accepted') if submission.accepted + return t('submission.too_late_rejected') end return t('submission.under_review') if submission&.manuscript return t('submission.no_file') if submission + return t('submission.nothing') end end def submission_status(submission, assignment) tag.i class: [submission_status_icon(submission, assignment), 'fa-lg'], - data: { toggle: 'tooltip'}, + data: { toggle: 'tooltip' }, title: submission_status_text(submission, assignment) end @@ -89,25 +103,29 @@ def show_submission_footer?(submission, assignment) return true if assignment.active? return false if assignment.totally_expired? return false if submission&.correction + true end def submission_late_color(submission) return '' unless submission.too_late? return '' unless submission.accepted.nil? + 'bg-submission-orange' end def late_submission_info(submission, tutorial) text = t('submission.late') return text unless submission.accepted.nil? && current_user.in?(tutorial.tutors) + "#{text} (#{t('tutorial.late_submission_decision')})" end def correction_display_mode(submission) - accepted = submission.assignment.accepted_file_type - non_inline = Assignment.non_inline_file_types - return t('buttons.show') unless accepted.in?(non_inline) - t('buttons.download') + accepted = submission.assignment.accepted_file_type + non_inline = Assignment.non_inline_file_types + return t('buttons.show') unless accepted.in?(non_inline) + + t('buttons.download') end -end \ No newline at end of file +end diff --git a/app/helpers/talks_helper.rb b/app/helpers/talks_helper.rb index be87e2625..3ef166bbf 100644 --- a/app/helpers/talks_helper.rb +++ b/app/helpers/talks_helper.rb @@ -7,16 +7,19 @@ def talk_positions_for_select(talk) def talk_card_color(talk, user) return 'bg-mdb-color-lighten-2' unless user.in?(talk.speakers) + 'bg-info' end def speaker_list(talk) return t('basics.tba') unless talk.speakers.present? + talk.speakers.map(&:tutorial_name).join(', ') end def speaker_icon_class(talk) return 'fas fa-user' unless talk.speakers.count > 1 + 'fas fa-users' end @@ -39,4 +42,4 @@ def date_list(talk) def cospeaker_list(talk, user) (talk.speakers.to_a - [user]).map(&:tutorial_name).join(', ') end -end \ No newline at end of file +end diff --git a/app/helpers/tutorials_helper.rb b/app/helpers/tutorials_helper.rb index da7657c1d..7cb929bf7 100644 --- a/app/helpers/tutorials_helper.rb +++ b/app/helpers/tutorials_helper.rb @@ -2,16 +2,18 @@ module TutorialsHelper def cancel_editing_tutorial_path(tutorial) return cancel_edit_tutorial_path(tutorial) if tutorial.persisted? + cancel_new_tutorial_path(params: { lecture: tutorial.lecture }) end def tutorial_preselection(tutorial) return [[]] unless tutorial.persisted? && tutorial.tutors.any? + options_for_select(tutorial.tutors.map { |t| [t.tutorial_info, t.id] }, tutorial.tutor_ids) end def tutorials_selection(lecture) - lecture.tutorials.map { |t| [t.title_with_tutors, t.id] } + lecture.tutorials.map { |t| [t.title_with_tutors, t.id] } end end diff --git a/app/helpers/vertices_helper.rb b/app/helpers/vertices_helper.rb index 5ce17d3dc..db0401c48 100644 --- a/app/helpers/vertices_helper.rb +++ b/app/helpers/vertices_helper.rb @@ -12,4 +12,4 @@ def vertices_labels(quiz, vertex_id, undefined) def crosses_id(crosses) crosses.keys.collect { |k| [k, crosses[k].to_s.first] }.flatten.join end -end \ No newline at end of file +end diff --git a/app/mailers/application_mailer.rb b/app/mailers/application_mailer.rb index 13fdba3ff..d8255248e 100644 --- a/app/mailers/application_mailer.rb +++ b/app/mailers/application_mailer.rb @@ -1,6 +1,8 @@ class ApplicationMailer < ActionMailer::Base helper EmailHelper default from: DefaultSetting::PROJECT_EMAIL - default "Message-ID" => -> {"<#{rand.to_s.split('.')[1]}.#{Time.now.to_i}@#{ENV['MAILID_DOMAIN']}>" } + default "Message-ID" => -> { + "<#{rand.to_s.split('.')[1]}.#{Time.now.to_i}@#{ENV['MAILID_DOMAIN']}>" + } layout 'mailer' end diff --git a/app/mailers/exception_handler/exception_mailer.rb b/app/mailers/exception_handler/exception_mailer.rb index dfe09b2aa..aa1913e52 100644 --- a/app/mailers/exception_handler/exception_mailer.rb +++ b/app/mailers/exception_handler/exception_mailer.rb @@ -1,6 +1,5 @@ module ExceptionHandler class ExceptionMailer < ActionMailer::Base - # Layout layout "exception_mailer" @@ -17,4 +16,4 @@ def new_exception e Rails.logger.info "Exception Sent To → #{ExceptionHandler.config.email}" end end -end \ No newline at end of file +end diff --git a/app/mailers/mathi_mailer.rb b/app/mailers/mathi_mailer.rb index f55eeb203..438fbe30e 100644 --- a/app/mailers/mathi_mailer.rb +++ b/app/mailers/mathi_mailer.rb @@ -4,6 +4,7 @@ class MathiMailer < ApplicationMailer def ghost_email(user) return if user.ghost_hash.nil? + @name = user.name @hash = user.ghost_hash mail(to: user.email, subject: t('mailer.hash_mail_subject')) @@ -17,7 +18,8 @@ def data_request_email(user) def data_provide_email(user) @user = user - mail(to: user.email, subject: t('mailer.data_provide_mail_subject')) do |format| + mail(to: user.email, + subject: t('mailer.data_provide_mail_subject')) do |format| format.html { render layout: 'mailer' } end end diff --git a/app/mailers/my_mailer.rb b/app/mailers/my_mailer.rb index 160ad930e..c57e0eb04 100644 --- a/app/mailers/my_mailer.rb +++ b/app/mailers/my_mailer.rb @@ -4,6 +4,8 @@ class MyMailer < Devise::Mailer layout "devise_mailer" default template_path: 'devise/mailer' # to make sure that your mailer uses the devise views default from: DefaultSetting::PROJECT_EMAIL - default "Message-ID" => -> {"<#{rand.to_s.split('.')[1]}.#{Time.now.to_i}@#{ENV['MAILID_DOMAIN']}>" } + default "Message-ID" => -> { + "<#{rand.to_s.split('.')[1]}.#{Time.now.to_i}@#{ENV['MAILID_DOMAIN']}>" + } helper EmailHelper end diff --git a/app/mailers/notification_mailer.rb b/app/mailers/notification_mailer.rb index 3f8d6eab4..03a4419b0 100644 --- a/app/mailers/notification_mailer.rb +++ b/app/mailers/notification_mailer.rb @@ -22,7 +22,6 @@ class NotificationMailer < ApplicationMailer only: [:submission_join_email, :submission_leave_email] - def medium_email @medium = params[:medium] mail(from: @sender, @@ -34,11 +33,11 @@ def medium_email def announcement_email @announcement = params[:announcement] @announcement_details = if @announcement.lecture.present? - t('in') + ' ' + - @announcement.lecture.title_for_viewers - else - t('mailer.mampf_news') - end + t('in') + ' ' + + @announcement.lecture.title_for_viewers + else + t('mailer.mampf_news') + end mail(from: @sender, bcc: @recipients.pluck(:email), subject: t('mailer.announcement_subject') + ' ' + @@ -50,9 +49,9 @@ def new_lecture_email mail(from: @sender, bcc: @recipients.pluck(:email), subject: t('mailer.new_lecture_subject', - title: @lecture.title_for_viewers)) + title: @lecture.title_for_viewers)) end - + def new_editor_email @lecture = params[:lecture] @recipient = params[:recipient] @@ -61,9 +60,9 @@ def new_editor_email mail(from: @sender, to: @recipient.email, subject: t('mailer.new_editor_subject', - title: @lecture.title_for_viewers)) + title: @lecture.title_for_viewers)) end - + def submission_invitation_email @recipient = params[:recipient] @assignment = params[:assignment] @@ -177,26 +176,26 @@ def submission_destruction_lecture_email private - def set_sender_and_locale - @sender = "#{t('mailer.notification')} <#{DefaultSetting::PROJECT_NOTIFICATION_EMAIL}>" - I18n.locale = params[:locale] - end + def set_sender_and_locale + @sender = "#{t('mailer.notification')} <#{DefaultSetting::PROJECT_NOTIFICATION_EMAIL}>" + I18n.locale = params[:locale] + end - def set_recipients - @recipients = User.where(id: params[:recipients]) - end + def set_recipients + @recipients = User.where(id: params[:recipients]) + end - def set_recipient_and_submission - @recipient = params[:recipient] - @submission = params[:submission] - @assignment = @submission.assignment - end + def set_recipient_and_submission + @recipient = params[:recipient] + @submission = params[:submission] + @assignment = @submission.assignment + end - def set_filename - @filename = params[:filename] - end + def set_filename + @filename = params[:filename] + end - def set_user - @user = params[:user] - end + def set_user + @user = params[:user] + end end diff --git a/app/models/ability.rb b/app/models/ability.rb index b96221856..fbf76c958 100644 --- a/app/models/ability.rb +++ b/app/models/ability.rb @@ -8,4 +8,4 @@ class Ability def initialize(user) end -end \ No newline at end of file +end diff --git a/app/models/announcement.rb b/app/models/announcement.rb index 43d7bc56e..04d8eae44 100644 --- a/app/models/announcement.rb +++ b/app/models/announcement.rb @@ -8,7 +8,7 @@ class Announcement < ApplicationRecord paginates_per 10 - scope :active_on_main, -> { where(on_main_page: true, lecture:nil) } + scope :active_on_main, -> { where(on_main_page: true, lecture: nil) } # does there (still) exist a notification for the announcement for # the given user diff --git a/app/models/answer.rb b/app/models/answer.rb index 4af1f90cf..7bc2403a1 100644 --- a/app/models/answer.rb +++ b/app/models/answer.rb @@ -12,6 +12,7 @@ def conditional_explanation(correct) unless correct return explanation.string_between_markers(':(inkorrekt:', ')') end + explanation.string_between_markers('(korrekt:', '):') end @@ -26,24 +27,24 @@ def text_join private - def question_not_orphaned? - throw(:abort) if question.answers.size == 1 - true - end + def question_not_orphaned? + throw(:abort) if question.answers.size == 1 + true + end - def update_quizzes - question.quiz_ids.each do |q| - quiz = Quiz.find(q) - quiz_graph = quiz.quiz_graph - vertices = quiz_graph.find_vertices(question) - vertices.each do |v| - quiz_graph.reset_vertex_answers_change(v) + def update_quizzes + question.quiz_ids.each do |q| + quiz = Quiz.find(q) + quiz_graph = quiz.quiz_graph + vertices = quiz_graph.find_vertices(question) + vertices.each do |v| + quiz_graph.reset_vertex_answers_change(v) + end + quiz.update(quiz_graph: quiz_graph) end - quiz.update(quiz_graph: quiz_graph) end - end - def touch_medium - question.becomes(Medium).update(updated_at: Time.now) - end + def touch_medium + question.becomes(Medium).update(updated_at: Time.now) + end end diff --git a/app/models/assignment.rb b/app/models/assignment.rb index 6ad38bdb6..0da21a6ad 100644 --- a/app/models/assignment.rb +++ b/app/models/assignment.rb @@ -30,9 +30,9 @@ def self.accepted_file_types inclusion: { in: Assignment.accepted_file_types } def submission(user) - UserSubmissionJoin.where(submission: Submission.where(assignment: self), - user: user) - &.first&.submission + UserSubmissionJoin.where(submission: Submission.where(assignment: self), + user: user) + &.first&.submission end def submitter_ids @@ -52,7 +52,7 @@ def semiactive? end def expired? - !active? + !active? end def totally_expired? @@ -65,27 +65,30 @@ def in_grace_period? def friendly_deadline return deadline unless lecture.submission_grace_period + deadline + lecture.submission_grace_period.minutes end def current? - self.in?(lecture.current_assignments) + self.in?(lecture.current_assignments) end def previous? - self.in?(lecture.previous_assignments) + self.in?(lecture.previous_assignments) end def previous siblings = lecture.assignments_by_deadline position = siblings.map(&:first).find_index(deadline) return unless position.positive? + siblings[position - 1].second end def submission_partners(user) submission = submission(user) return unless submission + submission.users - [user] end @@ -94,16 +97,17 @@ def tutorial(user) end def destructible? - submissions.proper.none? + submissions.proper.none? end def check_destructibility - throw(:abort) unless destructible? - true + throw(:abort) unless destructible? + true end def has_documents? return false unless medium + medium.video || medium.manuscript || medium.geogebra || medium.external_reference_link.present? || (medium.sort == 'Quiz' && medium.quiz_graph) @@ -141,12 +145,12 @@ def accepted_mime_types # is set to .tar.gz # see e.g. https://bugs.chromium.org/p/chromium/issues/detail?id=521781 def accepted_for_file_input - return accepted_file_type unless accepted_file_type == '.tar.gz' - '.gz' + return accepted_file_type unless accepted_file_type == '.tar.gz' + + '.gz' end def localized_deletion_date deletion_date.strftime(I18n.t('date.formats.concise')) end - end diff --git a/app/models/chapter.rb b/app/models/chapter.rb index 6841ff5e3..cad5d882c 100644 --- a/app/models/chapter.rb +++ b/app/models/chapter.rb @@ -16,13 +16,15 @@ def to_label number: displayed_number, title: title) end - I18n.t("hidden_#{lecture.chapter_name}", number: displayed_number, title: title) + I18n.t("hidden_#{lecture.chapter_name}", number: displayed_number, + title: title) end # Returns the number of the chapter. Unless the user explicitly specified # a display number, this number is calculated def displayed_number return calculated_number unless display_number.present? + display_number end @@ -35,6 +37,7 @@ def reference # Returns the chapter number based on the position in the chapters list. def calculated_number return position.to_s unless lecture.start_chapter.present? + (lecture.start_chapter + position - 1).to_s end diff --git a/app/models/clicker.rb b/app/models/clicker.rb index 13742f271..075918d04 100644 --- a/app/models/clicker.rb +++ b/app/models/clicker.rb @@ -36,14 +36,15 @@ def close! def results total = votes.count return unless total.positive? + (1..alternatives).map { |i| votes.where(value: i).count / total.to_f } .map { |x| (100 * x).round } end private - def set_basics - self.code = SecureRandom.uuid - self.alternatives = 3 - end + def set_basics + self.code = SecureRandom.uuid + self.alternatives = 3 + end end diff --git a/app/models/clicker_vote.rb b/app/models/clicker_vote.rb index 6c6a06046..8d73c024c 100644 --- a/app/models/clicker_vote.rb +++ b/app/models/clicker_vote.rb @@ -7,14 +7,15 @@ class ClickerVote < ApplicationRecord private - def clicker_open - return true if clicker.open? - errors.add(:clicker, :clicker_closed) - end + def clicker_open + return true if clicker.open? - def value_in_range - return true if value.in?(1..clicker.alternatives) - errors.add(:value, :out_of_range) - end -end + errors.add(:clicker, :clicker_closed) + end + + def value_in_range + return true if value.in?(1..clicker.alternatives) + errors.add(:value, :out_of_range) + end +end diff --git a/app/models/course_self_join.rb b/app/models/course_self_join.rb index 0353e1c48..7311f85eb 100644 --- a/app/models/course_self_join.rb +++ b/app/models/course_self_join.rb @@ -5,14 +5,14 @@ class CourseSelfJoin < ApplicationRecord belongs_to :course belongs_to :preceding_course, class_name: 'Course' - validates :preceding_course, uniqueness: { scope: :course} + validates :preceding_course, uniqueness: { scope: :course } # we do not allow a course to be preceding itself after_save :destroy, if: :self_inverse? private - def self_inverse? - course_id == preceding_course_id - end + def self_inverse? + course_id == preceding_course_id + end end diff --git a/app/models/course_tag_join.rb b/app/models/course_tag_join.rb index d97f91449..92a30f4e2 100644 --- a/app/models/course_tag_join.rb +++ b/app/models/course_tag_join.rb @@ -12,8 +12,9 @@ class CourseTagJoin < ApplicationRecord private - def touch_tag - return unless tag.present? && tag.persisted? - tag.touch - end + def touch_tag + return unless tag.present? && tag.persisted? + + tag.touch + end end diff --git a/app/models/division.rb b/app/models/division.rb index 148b5b9c6..bab3b37ce 100644 --- a/app/models/division.rb +++ b/app/models/division.rb @@ -3,12 +3,12 @@ class Division < ApplicationRecord has_many :division_course_joins has_many :courses, through: :division_course_joins - translates :name + translates :name - globalize_accessors locales: I18n.available_locales, - attributes: translated_attribute_names + globalize_accessors locales: I18n.available_locales, + attributes: translated_attribute_names - def name_with_program - "#{program.subject.name}:#{program.name}:#{name}" - end + def name_with_program + "#{program.subject.name}:#{program.name}:#{name}" + end end diff --git a/app/models/interaction.rb b/app/models/interaction.rb index 652ea675b..774e8e794 100644 --- a/app/models/interaction.rb +++ b/app/models/interaction.rb @@ -1,17 +1,19 @@ class Interaction < InteractionsRecord - - scope :created_between, lambda {|start_date, end_date| where(created_at: start_date.beginning_of_day..end_date.end_of_day)} + scope :created_between, lambda { |start_date, end_date| + where(created_at: start_date.beginning_of_day..end_date.end_of_day) + } require 'csv' def self.to_csv - attributes = %w{id session_id created_at full_path referrer_url study_participant} + attributes = %w{id session_id created_at full_path referrer_url + study_participant} CSV.generate(headers: true) do |csv| csv << attributes all.each do |interaction| - csv << attributes.map{ |attr| interaction.send(attr) } + csv << attributes.map { |attr| interaction.send(attr) } end end end -end \ No newline at end of file +end diff --git a/app/models/interactions_record.rb b/app/models/interactions_record.rb index d832e6d7b..77ae77892 100644 --- a/app/models/interactions_record.rb +++ b/app/models/interactions_record.rb @@ -2,4 +2,4 @@ class InteractionsRecord < ApplicationRecord self.abstract_class = true connects_to database: { writing: :interactions, reading: :interactions } -end \ No newline at end of file +end diff --git a/app/models/item.rb b/app/models/item.rb index ab01fce53..bef489836 100644 --- a/app/models/item.rb +++ b/app/models/item.rb @@ -62,6 +62,7 @@ class Item < ApplicationRecord def end_time return unless video? return TimeStamp.new(total_seconds: medium.video_duration) if next_item.nil? + TimeStamp.new(total_seconds: next_item.start_time.total_seconds - 0.001) end @@ -79,6 +80,7 @@ def vtt_time_span def vtt_text return '' if sort == 'pdf_destination' return description if sort == 'link' + short_description end @@ -88,6 +90,7 @@ def vtt_text # "Bem. 29.13: zu freien Moduln\n\n" def vtt_reference return short_description + "\n\n" unless short_reference.present? + short_reference + ': ' + short_description + "\n\n" end @@ -97,6 +100,7 @@ def vtt_reference # "Verweis auf LA 2 SS 17, Bem. 29.13:" def vtt_meta_reference(referring_medium) return I18n.t('item.external_reference') if sort == 'link' + ref = local?(referring_medium) ? short_reference : long_reference I18n.t('item.internal_reference', ref: ref) end @@ -106,6 +110,7 @@ def vtt_meta_reference(referring_medium) # "Bem. 29.13" def short_reference return math_reference if math_items.include?(sort) + toc_reference end @@ -116,6 +121,7 @@ def long_reference return short_reference if sort.in?(['self', 'link']) return short_ref_with_teachable if section.present? return medium.title_for_viewers unless short_reference.present? + medium.title_for_viewers + ', ' + short_reference end @@ -124,6 +130,7 @@ def long_reference def short_description return section.title if sort == 'section' && section.present? return medium.title_for_viewers if sort == 'self' + description.to_s end @@ -136,6 +143,7 @@ def short_description def local_reference unless sort.in?(['self', 'link', 'pdf_destination']) return short_ref_with_description unless medium&.sort == 'Script' + return 'Skript, ' + short_ref_with_description end local_non_math_reference @@ -148,6 +156,7 @@ def title_within_course return '' unless medium.present? && medium.proper? return local_reference if medium.teachable_type == 'Course' return local_reference unless medium.teachable.media_scope.term + medium.teachable.media_scope.term.to_label_short + ', ' + local_reference end @@ -165,23 +174,26 @@ def title_within_lecture # lesson's lecture def local?(referring_medium) return false unless section.present? + in?(referring_medium.teachable.lecture&.items.to_a) end # background color of different item sorts within thyme editor def background return '#70db70;' if ['remark', 'theorem', 'lemma', 'corollary', - 'algorithm', 'Theorem', 'Corollary', 'Lemma', - 'proposition'].include?(sort) + 'algorithm', 'Theorem', 'Corollary', 'Lemma', + 'proposition'].include?(sort) return '#75d7f0;' if ['definition', 'annotation', 'example', 'figure', 'exercise', 'equation'].include?(sort) return 'lightgray;' if sort == 'link' || sort == 'self' + '' end # special background for sections def section_background return 'beige;' if sort == 'section' + 'aliceblue;' end @@ -193,6 +205,7 @@ def video_link return if sort == 'pdf_destination' return unless video? return video_link_untimed if sort == 'self' + video_link_timed end @@ -206,23 +219,25 @@ def manuscript_link return unless manuscript? return manuscript_link_destination if pdf_destination.present? return manuscript_link_page if page.present? + manuscript_link_generic end def quiz_link return unless quiz? + return quiz_link_generic end - # if the associated medium contains an external link, it is returned def medium_link return unless medium_link? + medium.external_reference_link end def self.available_sorts - ['definition','remark', 'lemma', 'theorem', 'example', 'annotation', + ['definition', 'remark', 'lemma', 'theorem', 'example', 'annotation', 'algorithm', 'corollary', 'section', 'label', 'subsection', 'Theorem', 'proposition', 'Lemma', 'Corollary', 'figure', 'chapter', 'exercise', 'equation'] @@ -285,190 +300,205 @@ def related_items_visible? private - def math_items - ['remark', 'theorem', 'lemma', 'definition', 'annotation', 'example', - 'corollary', 'algorithm', 'Theorem', 'proposition', 'Lemma', 'Corollary', - 'figure', 'subsection', 'exercise', 'equation'] - end + def math_items + ['remark', 'theorem', 'lemma', 'definition', 'annotation', 'example', + 'corollary', 'algorithm', 'Theorem', 'proposition', 'Lemma', 'Corollary', + 'figure', 'subsection', 'exercise', 'equation'] + end - def other_items - ['section', 'self', 'link', 'label', 'pdf_destination', 'chapter'] - end + def other_items + ['section', 'self', 'link', 'label', 'pdf_destination', 'chapter'] + end - def proper_link? - sort == 'link' && link.present? - end + def proper_link? + sort == 'link' && link.present? + end - def next_item - medium.proper_items_by_time.find do |i| - i.start_time.total_seconds > start_time.total_seconds + def next_item + medium.proper_items_by_time.find do |i| + i.start_time.total_seconds > start_time.total_seconds + end end - end - def sort_long - I18n.t("admin.item.sort_short.#{sort}") - end + def sort_long + I18n.t("admin.item.sort_short.#{sort}") + end - # the next methods are used to put together the references and descriptions + # the next methods are used to put together the references and descriptions - def math_item_number - ref_number.to_s - end + def math_item_number + ref_number.to_s + end - def math_reference - sort_long + ' ' + math_item_number - end + def math_reference + sort_long + ' ' + math_item_number + end - def special_reference - return 'Medium' if sort == 'self' - return '' if sort == 'pdf_destination' - 'extern' - end + def special_reference + return 'Medium' if sort == 'self' + return '' if sort == 'pdf_destination' - def section_reference - return section.displayed_number.to_s if section.present? - return '§' + ref_number if ref_number.present? - '' - end + 'extern' + end - def chapter_reference - chapter_short = I18n.t('admin.item.chapter_short', - locale: locale) - return "#{chapter_short} #{ref_number}" if ref_number.present? - chapter_short - end + def section_reference + return section.displayed_number.to_s if section.present? + return '§' + ref_number if ref_number.present? - def toc_reference - return section_reference if sort == 'section' - return chapter_reference if sort == 'chapter' - if sort == 'label' - return '' if description.present? - return 'destination: ' + pdf_destination.to_s + '' end - special_reference - end - def non_math_reference - return medium.title_for_viewers if sort == 'self' - if sort == 'pdf_destination' - return medium.title_for_viewers + ' (pdf) # ' + description + def chapter_reference + chapter_short = I18n.t('admin.item.chapter_short', + locale: locale) + return "#{chapter_short} #{ref_number}" if ref_number.present? + + chapter_short end - 'extern ' + description.to_s if sort == 'link' - end - def local_non_math_reference - return medium.local_title_for_viewers if sort == 'self' - if sort == 'pdf_destination' - return medium.local_title_for_viewers + ' (pdf) # ' + description + def toc_reference + return section_reference if sort == 'section' + return chapter_reference if sort == 'chapter' + + if sort == 'label' + return '' if description.present? + + return 'destination: ' + pdf_destination.to_s + end + special_reference end - 'extern ' + description.to_s if sort == 'link' - end - def short_ref_with_teachable - unless short_reference.present? - return medium.teachable.lecture.title_for_viewers + def non_math_reference + return medium.title_for_viewers if sort == 'self' + if sort == 'pdf_destination' + return medium.title_for_viewers + ' (pdf) # ' + description + end + + 'extern ' + description.to_s if sort == 'link' end - medium.teachable.lecture.title_for_viewers + ', ' + short_reference - end - def short_ref_with_description - return short_reference + ' ' + description.to_s unless sort == 'section' - short_ref_for_sections - end + def local_non_math_reference + return medium.local_title_for_viewers if sort == 'self' + if sort == 'pdf_destination' + return medium.local_title_for_viewers + ' (pdf) # ' + description + end - def short_ref_for_sections - return short_reference + ' ' + description if description.present? - return short_reference + ' ' + section.title if section.present? - short_reference - end + 'extern ' + description.to_s if sort == 'link' + end - # the next two methods get video links using helper methods + def short_ref_with_teachable + unless short_reference.present? + return medium.teachable.lecture.title_for_viewers + end - def video_link_untimed - Rails.application.routes.url_helpers.play_medium_path(medium.id) - end + medium.teachable.lecture.title_for_viewers + ', ' + short_reference + end - def video_link_timed - Rails.application.routes.url_helpers - .play_medium_path(medium.id, time: start_time.total_seconds) - end + def short_ref_with_description + return short_reference + ' ' + description.to_s unless sort == 'section' - def manuscript_link_generic - Rails.application.routes.url_helpers.display_medium_path(medium.id) - end + short_ref_for_sections + end - def manuscript_link_destination - Rails.application.routes.url_helpers - .display_medium_path(medium.id, destination: pdf_destination) - end + def short_ref_for_sections + return short_reference + ' ' + description if description.present? + return short_reference + ' ' + section.title if section.present? - def manuscript_link_page - Rails.application.routes.url_helpers - .display_medium_path(medium.id, page: page) - end + short_reference + end - def quiz_link_generic - Rails.application.routes.url_helpers.take_quiz_path(medium.id) - end + # the next two methods get video links using helper methods - # the next methods are used for validations + def video_link_untimed + Rails.application.routes.url_helpers.play_medium_path(medium.id) + end - def valid_start_time - return true if start_time.nil? - return true if start_time.valid? - errors.add(:start_time, :invalid_format) - false - end + def video_link_timed + Rails.application.routes.url_helpers + .play_medium_path(medium.id, time: start_time.total_seconds) + end - def start_time_not_required - medium.nil? || medium.sort == 'Script' || sort == 'self' || - sort == 'pdf_destination' || !start_time&.valid? || !medium.video - end + def manuscript_link_generic + Rails.application.routes.url_helpers.display_medium_path(medium.id) + end - def start_time_not_too_late - return true if start_time_not_required - return true if start_time.total_seconds <= medium.video.metadata['duration'] - errors.add(:start_time, :too_late) - false - end + def manuscript_link_destination + Rails.application.routes.url_helpers + .display_medium_path(medium.id, destination: pdf_destination) + end - def start_times_without - (medium.proper_items - [self]).map do |i| - [i.start_time.floor_seconds, i.start_time.milliseconds] + def manuscript_link_page + Rails.application.routes.url_helpers + .display_medium_path(medium.id, page: page) end - end - def no_duplicate_start_time - return true if start_time_not_required - if start_times_without.include?([start_time.floor_seconds, - start_time.milliseconds]) - errors.add(:start_time, :taken) + def quiz_link_generic + Rails.application.routes.url_helpers.take_quiz_path(medium.id) + end + + # the next methods are used for validations + + def valid_start_time + return true if start_time.nil? + return true if start_time.valid? + + errors.add(:start_time, :invalid_format) false end - true - end - def nonempty_link_or_explanation - return true if sort != 'link' - return true if link.present? - return true if explanation.present? - errors.add(:link, :blank) - errors.add(:explanation, :blank) - end + def start_time_not_required + medium.nil? || medium.sort == 'Script' || sort == 'self' || + sort == 'pdf_destination' || !start_time&.valid? || !medium.video + end - # is used for after save and before destroy callbacks - def touch_medium - return unless medium.present? && medium.persisted? - medium.touch - end + def start_time_not_too_late + return true if start_time_not_required + return true if start_time.total_seconds <= medium.video.metadata['duration'] - # simulates the after_destroy callback for item_self_joins - def destroy_joins(related_item) - ItemSelfJoin.where(item: [self, related_item], - related_item: [self, related_item]).delete_all - end + errors.add(:start_time, :too_late) + false + end - def locale - medium&.locale || I18n.default_locale - end + def start_times_without + (medium.proper_items - [self]).map do |i| + [i.start_time.floor_seconds, i.start_time.milliseconds] + end + end + + def no_duplicate_start_time + return true if start_time_not_required + + if start_times_without.include?([start_time.floor_seconds, + start_time.milliseconds]) + errors.add(:start_time, :taken) + false + end + true + end + + def nonempty_link_or_explanation + return true if sort != 'link' + return true if link.present? + return true if explanation.present? + + errors.add(:link, :blank) + errors.add(:explanation, :blank) + end + + # is used for after save and before destroy callbacks + def touch_medium + return unless medium.present? && medium.persisted? + + medium.touch + end + + # simulates the after_destroy callback for item_self_joins + def destroy_joins(related_item) + ItemSelfJoin.where(item: [self, related_item], + related_item: [self, related_item]).delete_all + end + + def locale + medium&.locale || I18n.default_locale + end end diff --git a/app/models/item_self_join.rb b/app/models/item_self_join.rb index be448bacc..8f03219d2 100644 --- a/app/models/item_self_join.rb +++ b/app/models/item_self_join.rb @@ -16,32 +16,33 @@ class ItemSelfJoin < ApplicationRecord private - def create_inverse - self.class.create(inverse_relation_options) - end - - def destroy_inverses - inverses.destroy_all - end - - def self_inverse? - item_id == related_item_id - end - - def inverse? - self.class.exists?(inverse_relation_options) || self_inverse? - end - - def inverses - self.class.where(inverse_relation_options) - end - - def inverse_relation_options - { related_item_id: item_id, item_id: related_item_id } - end - - def touch_item - return if item.nil? - item.touch - end + def create_inverse + self.class.create(inverse_relation_options) + end + + def destroy_inverses + inverses.destroy_all + end + + def self_inverse? + item_id == related_item_id + end + + def inverse? + self.class.exists?(inverse_relation_options) || self_inverse? + end + + def inverses + self.class.where(inverse_relation_options) + end + + def inverse_relation_options + { related_item_id: item_id, item_id: related_item_id } + end + + def touch_item + return if item.nil? + + item.touch + end end diff --git a/app/models/lecture.rb b/app/models/lecture.rb index 4f510aecf..c8326b500 100644 --- a/app/models/lecture.rb +++ b/app/models/lecture.rb @@ -16,9 +16,9 @@ class Lecture < ApplicationRecord # during the term, a lot of lessons take place for this lecture has_many :lessons, -> { order(date: :asc, id: :asc) }, - dependent: :destroy, - after_add: :touch_siblings, - after_remove: :touch_siblings + dependent: :destroy, + after_add: :touch_siblings, + after_remove: :touch_siblings # a lecture has many talks, which have positions has_many :talks, -> { order(position: :asc) }, dependent: :destroy @@ -37,7 +37,7 @@ class Lecture < ApplicationRecord # a lecture has many users who have starred it (fans) has_many :user_favorite_lecture_joins, dependent: :destroy has_many :fans, -> { distinct }, through: :user_favorite_lecture_joins, - source: :user + source: :user # a lecture has many editors # these are users different from the teacher who have the right to @@ -84,7 +84,6 @@ class Lecture < ApplicationRecord greater_than: -1 }, allow_nil: true - # as a teacher has editing rights by definition, we do not need him in the # list of editors after_save :remove_teacher_as_editor @@ -161,11 +160,13 @@ def selector_value def title return course.title unless term + "(#{sort_localized_short}) #{course.title}, #{term.to_label}" end def title_no_term return course.title unless term + "(#{sort_localized_short}) #{course.title}" end @@ -179,6 +180,7 @@ def to_label def compact_title return course.compact_title unless term + "#{sort_localized_short}.#{course.compact_title}.#{term.compact_title}" end @@ -202,6 +204,7 @@ def card_header def card_header_path(user) return unless user.lectures.include?(self) + lecture_path end @@ -218,6 +221,7 @@ def visible_for_user?(user) return true if edited_by?(user) return false unless published? return false if restricted? && !self.in?(user.lectures) + true end @@ -276,6 +280,7 @@ def items # and items in quarantine def script_items_by_position return [] unless manuscript + hidden_chapters = Chapter.where(hidden: true) hidden_sections = Section.where(hidden: true) .or(Section.where(chapter: hidden_chapters)) @@ -296,8 +301,8 @@ def manuscript def media_with_inheritance_uncached Medium.proper.where(teachable: self) - .or(Medium.proper.where(teachable: self.lessons)) - .or(Medium.proper.where(teachable: self.talks)) + .or(Medium.proper.where(teachable: self.lessons)) + .or(Medium.proper.where(teachable: self.talks)) end def media_with_inheritance_uncached_eagerload_stuff @@ -307,7 +312,6 @@ def media_with_inheritance_uncached_eagerload_stuff .proper.where(teachable: self.lessons + self.talks)) end - def media_with_inheritance Rails.cache.fetch("#{cache_key_with_version}/media_with_inheritance") do media_with_inheritance_uncached @@ -363,32 +367,36 @@ def reste?(user) project?('reste', user) || imported_any?('reste') end - # the next methods put together some information on the lecture (teacher, # term, title) in various combinations def short_title return course.short_title unless term + "(#{sort_localized_short}) #{course.short_title} #{term.to_label_short}" end def short_title_release return short_title if published? + "#{short_title} (#{I18n.t('access.unpublished')})" end def short_title_brackets return course.short_title unless term + "(#{sort_localized_short}) #{course.short_title} (#{term.to_label_short})" end def title_with_teacher return title unless teacher.present? && teacher.name.present? + "#{title} (#{teacher.name})" end def title_with_teacher_no_type return "#{course.title}, (#{teacher.name})" unless term + "#{course.title}, #{term.to_label} (#{teacher.name})" end @@ -396,11 +404,13 @@ def term_teacher_info return term_to_label unless teacher.present? return term_to_label unless teacher.name.present? return "#{course.title}, #{teacher.name}" unless term + "(#{sort_localized_short}) #{term_to_label}, #{teacher.name}" end def term_teacher_published_info return term_teacher_info if published? + "#{term_teacher_info} (#{I18n.t('access.unpublished')})" end @@ -410,11 +420,13 @@ def title_term_info def title_term_info_no_type return course.title unless term + "#{course.title}, #{term_to_label}" end def title_teacher_info return course.title unless teacher.present? && teacher.name.present? + "(#{sort_localized_short}) #{course.title} (#{teacher.name})" end @@ -501,12 +513,14 @@ def editors_with_inheritance def edited_by?(user) return true if editors_with_inheritance.include?(user) + false end # returns path for show action of the lecture's course, def path(user) return unless user.lectures.include?(self) + Rails.application.routes.url_helpers .lecture_path(self) end @@ -537,8 +551,8 @@ def lecture_lesson_results(filtered_media) lecture_results + lesson_results.includes(:teachable) .sort_by do |m| - [order_factor*m.lesson.date.jd, - order_factor*m.lesson.id, + [order_factor * m.lesson.date.jd, + order_factor * m.lesson.id, m.position] end + talk_results.includes(:teachable).sort_by do |m| @@ -549,6 +563,7 @@ def lecture_lesson_results(filtered_media) def order_factor return -1 unless lecture.term.present? return -1 if lecture.term.active + 1 end @@ -580,6 +595,7 @@ def forum # by the user def unread_forum_topics_count(user) return unless forum? + forum_relation = Thredded::Messageboard.where(id: forum_id) forum_view = Thredded::MessageboardGroupView.grouped(forum_relation, @@ -609,11 +625,13 @@ def self.select_sorts def seminar? return true if sort.in?(['seminar', 'proseminar', 'oberseminar']) + false end def chapter_name return 'chapter' unless seminar? + 'talk' end @@ -628,7 +646,7 @@ def close_comments!(user) end def open_comments!(user) - media_with_inheritance.select { |m| m.commontator_thread.is_closed?} + media_with_inheritance.select { |m| m.commontator_thread.is_closed? } .each { |m| m.commontator_thread.reopen } end @@ -640,10 +658,11 @@ def <=>(other) return 0 if self == other return 1 if self.begin_date < other.begin_date return 1 if self.term == other.term && - ActiveSupport::Inflector.transliterate(self.course.title) > - ActiveSupport::Inflector.transliterate(other.course.title) + ActiveSupport::Inflector.transliterate(self.course.title) > + ActiveSupport::Inflector.transliterate(other.course.title) return 1 if self.term == other.term && self.course == other.course && - self.sort_localized < other.sort_localized + self.sort_localized < other.sort_localized + -1 end @@ -652,10 +671,14 @@ def subscribed_by?(user) end def self.search_by(search_params, page) - search_params[:types] = [] if search_params[:all_types] == '1' || search_params[:types].nil? - search_params[:term_ids] = [] if search_params[:all_terms] == '1' || search_params[:term_ids].nil? - search_params[:teacher_ids] = [] if search_params[:all_teachers] == '1' || search_params[:teacher_ids].nil? - search_params[:program_ids] = [] if search_params[:all_programs] == '1' || search_params[:program_ids].nil? + search_params[:types] = + [] if search_params[:all_types] == '1' || search_params[:types].nil? + search_params[:term_ids] = + [] if search_params[:all_terms] == '1' || search_params[:term_ids].nil? + search_params[:teacher_ids] = + [] if search_params[:all_teachers] == '1' || search_params[:teacher_ids].nil? + search_params[:program_ids] = + [] if search_params[:all_programs] == '1' || search_params[:program_ids].nil? search = Sunspot.new_search(Lecture) # add lectures without term to current term if Term.active.try(:id).to_i.to_s.in?(search_params[:term_ids]) @@ -663,9 +686,12 @@ def self.search_by(search_params, page) end search.build do with(:sort, search_params[:types]) unless search_params[:types].empty? - with(:teacher_id, search_params[:teacher_ids]) unless search_params[:teacher_ids].empty? - with(:program_ids, search_params[:program_ids]) unless search_params[:program_ids].empty? - with(:term_id, search_params[:term_ids]) unless search_params[:term_ids].empty? + with(:teacher_id, + search_params[:teacher_ids]) unless search_params[:teacher_ids].empty? + with(:program_ids, + search_params[:program_ids]) unless search_params[:program_ids].empty? + with(:term_id, + search_params[:term_ids]) unless search_params[:term_ids].empty? end admin = User.find_by_id(search_params[:user_id])&.admin unless admin @@ -692,11 +718,13 @@ def self.search_by(search_params, page) def term_to_label return term.to_label if term + '' end def term_to_label_short return term.to_label_short if term + '' end @@ -727,13 +755,13 @@ def previous_assignments def scheduled_assignments? media.where(sort: 'Nuesse').where.not(publisher: nil) - .any? { |m| m.publisher.create_assignment } + .any? { |m| m.publisher.create_assignment } end def scheduled_assignments media.where(sort: 'Nuesse').where.not(publisher: nil) - .select { |m| m.publisher.create_assignment } - .map { |m| m.publisher.assignment } + .select { |m| m.publisher.create_assignment } + .map { |m| m.publisher.assignment } end def assignments? @@ -762,111 +790,118 @@ def importable_toc? def import_toc!(imported_lecture, import_sections, import_tags) return unless imported_lecture + imported_lecture.chapters.each do |c| new_chapter = c.dup new_chapter.lecture = self new_chapter.save next unless import_sections + c.sections.each { |s| s.duplicate_in_chapter(new_chapter, import_tags) } end end private - # used for after save callback - def remove_teacher_as_editor - editors.delete(teacher) - end - - # looks in the cache if there are any media associated *with inheritance* - # to this lecture and a given project (kaviar, sesam etc.) - def project_as_user?(project) - Rails.cache.fetch("#{cache_key_with_version}/#{project}") do - Medium.where(sort: medium_sort[project], - released: ['all', 'users', 'subscribers'], - teachable: self).exists? || - Medium.where(sort: medium_sort[project], - released: ['all', 'users', 'subscribers'], - teachable: lessons).exists? || - Medium.where(sort: medium_sort[project], - released: ['all', 'users', 'subscribers'], - teachable: talks).exists? || - Medium.where(sort: medium_sort[project], - released: ['all', 'users', 'subscribers'], - teachable: course).exists? - end - end - - def imported_any?(project) - Rails.cache.fetch("#{cache_key_with_version}/imported_#{project}") do - imported_media.exists?(sort: medium_sort[project], - released: ['all', 'users']) - end - end - - def project?(project, user) - return project_as_user?(project) unless edited_by?(user) || user.admin - course_media = if user.in?(course.editors) || user.admin - Medium.where(sort: medium_sort[project], - teachable: course).exists? - else - Medium.where(sort: medium_sort[project], - released: ['all', 'users', 'subscribers'], - teachable: course).exists? - end - lecture_media = Medium.where(sort: medium_sort[project], - teachable: self).exists? - lesson_media = Medium.where(sort: medium_sort[project], - teachable: lessons).exists? - talk_media = Medium.where(sort: medium_sort[project], + # used for after save callback + def remove_teacher_as_editor + editors.delete(teacher) + end + + # looks in the cache if there are any media associated *with inheritance* + # to this lecture and a given project (kaviar, sesam etc.) + def project_as_user?(project) + Rails.cache.fetch("#{cache_key_with_version}/#{project}") do + Medium.where(sort: medium_sort[project], + released: ['all', 'users', 'subscribers'], + teachable: self).exists? || + Medium.where(sort: medium_sort[project], + released: ['all', 'users', 'subscribers'], + teachable: lessons).exists? || + Medium.where(sort: medium_sort[project], + released: ['all', 'users', 'subscribers'], + teachable: talks).exists? || + Medium.where(sort: medium_sort[project], + released: ['all', 'users', 'subscribers'], + teachable: course).exists? + end + end + + def imported_any?(project) + Rails.cache.fetch("#{cache_key_with_version}/imported_#{project}") do + imported_media.exists?(sort: medium_sort[project], + released: ['all', 'users']) + end + end + + def project?(project, user) + return project_as_user?(project) unless edited_by?(user) || user.admin + + course_media = if user.in?(course.editors) || user.admin + Medium.where(sort: medium_sort[project], + teachable: course).exists? + else + Medium.where(sort: medium_sort[project], + released: ['all', 'users', 'subscribers'], + teachable: course).exists? + end + lecture_media = Medium.where(sort: medium_sort[project], + teachable: self).exists? + lesson_media = Medium.where(sort: medium_sort[project], + teachable: lessons).exists? + talk_media = Medium.where(sort: medium_sort[project], teachable: talks).exists? - course_media || lecture_media || lesson_media || talk_media - end + course_media || lecture_media || lesson_media || talk_media + end - def medium_sort - { 'kaviar' => ['Kaviar'], 'sesam' => ['Sesam'], 'kiwi' => ['Kiwi'], - 'keks' => ['Quiz'], 'nuesse' => ['Nuesse'], - 'erdbeere' => ['Erdbeere'], 'script' => ['Script'], 'reste' => ['Reste']} - end + def medium_sort + { 'kaviar' => ['Kaviar'], 'sesam' => ['Sesam'], 'kiwi' => ['Kiwi'], + 'keks' => ['Quiz'], 'nuesse' => ['Nuesse'], + 'erdbeere' => ['Erdbeere'], 'script' => ['Script'], 'reste' => ['Reste'] } + end - def touch_media - media_with_inheritance.update_all(updated_at: Time.now) - end + def touch_media + media_with_inheritance.update_all(updated_at: Time.now) + end - def touch_lessons - lessons.update_all(updated_at: Time.now) - end + def touch_lessons + lessons.update_all(updated_at: Time.now) + end - def touch_siblings(lesson) - lessons.update_all(updated_at: Time.now) - Medium.where(teachable: lessons).update_all(updated_at: Time.now) - end + def touch_siblings(lesson) + lessons.update_all(updated_at: Time.now) + Medium.where(teachable: lessons).update_all(updated_at: Time.now) + end - def touch_chapters - chapters.update_all(updated_at: Time.now) - end + def touch_chapters + chapters.update_all(updated_at: Time.now) + end - def touch_sections - Section.where(chapter: chapters).update_all(updated_at: Time.now) - end + def touch_sections + Section.where(chapter: chapters).update_all(updated_at: Time.now) + end - def destroy_forum - return unless forum - forum.destroy - end + def destroy_forum + return unless forum - def term_independent? - return false unless course - course.term_independent - end + forum.destroy + end - def absence_of_term - return unless term - errors.add(:term, :present) - end + def term_independent? + return false unless course - def only_one_lecture - return unless Lecture.where(course: course).any? - errors.add(:course, :already_present) - end + course.term_independent + end + + def absence_of_term + return unless term + + errors.add(:term, :present) + end + + def only_one_lecture + return unless Lecture.where(course: course).any? + + errors.add(:course, :already_present) + end end diff --git a/app/models/lesson.rb b/app/models/lesson.rb index 010568033..852e1dfff 100644 --- a/app/models/lesson.rb +++ b/app/models/lesson.rb @@ -11,8 +11,8 @@ class Lesson < ApplicationRecord # taught in the lesson has_many :lesson_section_joins, dependent: :destroy has_many :sections, through: :lesson_section_joins, - after_remove: :touch_section, - after_add: :touch_section + after_remove: :touch_section, + after_add: :touch_section # being a teachable (course/lecture/lesson), a lesson has associated media has_many :media, -> { order(position: :asc) }, @@ -40,6 +40,7 @@ class Lesson < ApplicationRecord def course return unless lecture.present? + lecture.course end @@ -100,6 +101,7 @@ def card_header def card_header_path(user) return unless user.lectures.include?(lecture) + lesson_path end @@ -133,11 +135,13 @@ def restricted? def term return unless lecture.present? + lecture.term end def previous return unless number > 1 + lecture.lessons[number - 2] end @@ -149,7 +153,7 @@ def published_media media.published end - # visible media are published with inheritance and unlocked + # visible media are published with inheritance and unlocked def visible_media_for_user(user) media.select { |m| m.visible_for_user?(user) } end @@ -198,6 +202,7 @@ def visible_items def content_items return visible_items if lecture.content_mode == 'video' + script_items end @@ -207,6 +212,7 @@ def content def singular_medium return false if media.count != 1 + media.first end @@ -214,13 +220,16 @@ def singular_medium # (relevant if lecture content mode is manuscript) def script_items return [] unless lecture.manuscript && start_destination && end_destination + start_item = Item.where(medium: lecture.manuscript, pdf_destination: start_destination)&.first end_item = Item.where(medium: lecture.manuscript, - pdf_destination: end_destination)&.first + pdf_destination: end_destination)&.first return [] unless start_item && end_item + range = (start_item.position..end_item.position).to_a return [] unless range.present? + hidden_chapters = Chapter.where(hidden: true) hidden_sections = Section.where(hidden: true) .or(Section.where(chapter: hidden_chapters)) @@ -260,12 +269,14 @@ def self.editable_selection(user) def guess_start_destination return start_destination if start_destination return unless previous + probable_start_destination end def guess_end_destination return end_destination if end_destination return unless previous + probable_start_destination end @@ -273,11 +284,14 @@ def probable_start_destination end_item = Item.where(medium: lecture.manuscript, pdf_destination: previous.end_destination)&.first return unless end_item + position = end_item.position return unless position + successor = lecture.script_items_by_position.where('position > ?', position) .order(:position)&.first&.pdf_destination return successor if successor + end_item.pdf_destination end @@ -287,37 +301,37 @@ def tags_without_section private - # path for show lesson action - def lesson_path - Rails.application.routes.url_helpers.lesson_path(self) - end + # path for show lesson action + def lesson_path + Rails.application.routes.url_helpers.lesson_path(self) + end - # used for after save callback - def touch_media - lecture.media_with_inheritance.update_all(updated_at: Time.now) - end + # used for after save callback + def touch_media + lecture.media_with_inheritance.update_all(updated_at: Time.now) + end - def touch_siblings - lecture.lessons.update_all(updated_at: Time.now) - end + def touch_siblings + lecture.lessons.update_all(updated_at: Time.now) + end - def touch_sections - sections.update_all(updated_at: Time.now) - chapters = sections.map(&:chapter) - sections.map(&:chapter).each(&:touch) - lecture.touch - end + def touch_sections + sections.update_all(updated_at: Time.now) + chapters = sections.map(&:chapter) + sections.map(&:chapter).each(&:touch) + lecture.touch + end - def touch_self - touch - end + def touch_self + touch + end - def touch_tags - tags.update_all(updated_at: Time.now) - end + def touch_tags + tags.update_all(updated_at: Time.now) + end - def touch_section(section) - section.touch - section.chapter.touch - end + def touch_section(section) + section.touch + section.chapter.touch + end end diff --git a/app/models/link.rb b/app/models/link.rb index 8511878ee..285eef6c0 100644 --- a/app/models/link.rb +++ b/app/models/link.rb @@ -17,27 +17,27 @@ class Link < ApplicationRecord private - def self_inverse? - medium_id == linked_medium_id - end + def self_inverse? + medium_id == linked_medium_id + end - def create_inverse - self.class.create(inverse_link_options) - end + def create_inverse + self.class.create(inverse_link_options) + end - def destroy_inverses - inverses.destroy_all - end + def destroy_inverses + inverses.destroy_all + end - def inverse? - self.class.exists?(inverse_link_options) || self_inverse? - end + def inverse? + self.class.exists?(inverse_link_options) || self_inverse? + end - def inverses - self.class.where(inverse_link_options) - end + def inverses + self.class.where(inverse_link_options) + end - def inverse_link_options - { linked_medium_id: medium_id, medium_id: linked_medium_id } - end + def inverse_link_options + { linked_medium_id: medium_id, medium_id: linked_medium_id } + end end diff --git a/app/models/mampf_expression.rb b/app/models/mampf_expression.rb index 4122ab91b..b0c09afc1 100644 --- a/app/models/mampf_expression.rb +++ b/app/models/mampf_expression.rb @@ -16,4 +16,4 @@ def self.trivial_instance def self.from_hash(content) MampfExpression.new(content['0'], content['tex'], content['nerd']) end -end \ No newline at end of file +end diff --git a/app/models/mampf_matrix.rb b/app/models/mampf_matrix.rb index 4e2a712a1..fbe5c1562 100644 --- a/app/models/mampf_matrix.rb +++ b/app/models/mampf_matrix.rb @@ -18,11 +18,12 @@ def self.trivial_instance 'matrix([0,0],[0,0]') end - def entry(i,j) + def entry(i, j) if i > @row_count || j > @column_count return '0' end - @coefficients[(i - 1) * @column_count + (j - 1)] + + @coefficients[(i - 1) * @column_count + (j - 1)] end def self.from_hash(content) diff --git a/app/models/mampf_set.rb b/app/models/mampf_set.rb index 2b64143b7..333e8d08a 100644 --- a/app/models/mampf_set.rb +++ b/app/models/mampf_set.rb @@ -16,4 +16,4 @@ def self.trivial_instance def self.from_hash(content) MampfSet.new(content['0'], content['tex'], content['nerd']) end -end \ No newline at end of file +end diff --git a/app/models/mampf_tuple.rb b/app/models/mampf_tuple.rb index df4213fcd..dd997f92a 100644 --- a/app/models/mampf_tuple.rb +++ b/app/models/mampf_tuple.rb @@ -16,4 +16,4 @@ def self.trivial_instance def self.from_hash(content) MampfTuple.new(content['0'], content['tex'], content['nerd']) end -end \ No newline at end of file +end diff --git a/app/models/manuscript.rb b/app/models/manuscript.rb index 860477733..f11e245e7 100644 --- a/app/models/manuscript.rb +++ b/app/models/manuscript.rb @@ -13,6 +13,7 @@ def initialize(medium) medium.manuscript return end + @medium = medium @lecture = medium.teachable.lecture @locale = @lecture.locale_with_inheritance || I18n.default_locale @@ -90,6 +91,7 @@ def manuscript_chapter_contradicts?(chapter) # - creates items and tags for new content, depending on the users choices def export_to_db!(filter_boxes) return unless @contradiction_count.zero? + create_new_chapters! @chapters.each do |c| create_new_sections!(c) @@ -126,6 +128,7 @@ def create_new_chapters! # create sections in mampf for those manuscript sections not yet in mampf def create_new_sections!(chapter) return if chapter['mampf_chapter'].nil? + mampf_chapter = chapter['mampf_chapter'] new_sections_in_chapter(chapter).each do |s| sect = Section.new(chapter_id: mampf_chapter.id, title: s.second) @@ -156,7 +159,8 @@ def create_or_update_chapter_items! description: c['description'], ref_number: c['label'], position: nil, - quarantine: nil }) + quarantine: nil } + ) end create_or_update_items!(contents, item_details, item_destinations, item_id_map) @@ -185,7 +189,8 @@ def create_or_update_section_items! description: s['description'], ref_number: s['label'], position: -1, - quarantine: nil }) + quarantine: nil } + ) end create_or_update_items!(contents, item_details, item_destinations, item_id_map) @@ -217,7 +222,8 @@ def create_or_update_content_items!(filter_boxes) ref_number: c['label'], position: c['counter'], hidden: filter_boxes[c['counter']].third == false, - quarantine: nil }) + quarantine: nil } + ) end create_or_update_items!(contents, item_details, item_destinations, item_id_map) @@ -257,11 +263,13 @@ def update_tags!(filter_boxes) next unless tag next unless section next if section.in?(tag.sections) + tag.sections |= [section] tag.courses |= [@lecture.course] next end next unless filter_boxes[c['counter']].second + # if checkbox for tag creation is checked, create the tag, # associate it with course and section tag = Tag.new(courses: [@lecture.course], @@ -316,7 +324,7 @@ def add_info_on_item_ids_and_hidden_status end end -# private + # private def get_chapters(bookmarks) bookmarks.select { |b| b['sort'] == @chapter_marker } @@ -340,10 +348,10 @@ def match_mampf_chapters c['mampf_chapter'] = mampf_chapter c['contradiction'] = if mampf_chapter.nil? || mampf_chapter.title == c['description'] - false - else - :different_title - end + false + else + :different_title + end end end @@ -359,10 +367,10 @@ def match_mampf_sections s['mampf_section'] = mampf_section s['contradiction'] = if mampf_section.nil? || mampf_section.title == s['description'] - false - else - :different_title - end + false + else + :different_title + end end end @@ -371,12 +379,12 @@ def check_content bookmarked_chapter_counters = @chapters.map { |c| c['chapter'] } @content.each do |c| c['contradiction'] = if !c['chapter'].in?(bookmarked_chapter_counters) - :missing_chapter - elsif !c['section'].in?(bookmarked_section_counters) - :missing_section - else - false - end + :missing_chapter + elsif !c['section'].in?(bookmarked_section_counters) + :missing_section + else + false + end end end @@ -396,6 +404,7 @@ def determine_contradiction_count def version_info return [] if @version == DefaultSetting::MAMPF_STY_VERSION + [@version.to_s] end diff --git a/app/models/medium.rb b/app/models/medium.rb index 687e24a37..8da3bfc4f 100644 --- a/app/models/medium.rb +++ b/app/models/medium.rb @@ -40,12 +40,11 @@ class Medium < ApplicationRecord has_many :referrals, dependent: :destroy has_many :referenced_items, through: :referrals, source: :item - has_many :imports, dependent: :destroy has_many :importing_lectures, through: :imports, - source: :teachable, source_type: 'Lecture' + source: :teachable, source_type: 'Lecture' has_many :importing_courses, through: :imports, - source: :teachable, source_type: 'Course' + source: :teachable, source_type: 'Course' has_many :quiz_certificates, foreign_key: 'quiz_id', dependent: :destroy @@ -120,9 +119,13 @@ class Medium < ApplicationRecord # (they may not be globally visible as their lecture may be unpublished) scope :published, -> { where.not(released: nil) } scope :locally_visible, -> { where(released: ['all', 'users']) } - scope :potentially_visible, -> { where(released: ['all', 'users', 'subscribers']) } + scope :potentially_visible, -> { + where(released: ['all', 'users', 'subscribers']) + } scope :proper, -> { where.not(sort: 'RandomQuiz') } - scope :expired, -> { where(sort: 'RandomQuiz').where('created_at < ?', 1.day.ago) } + scope :expired, -> { + where(sort: 'RandomQuiz').where('created_at < ?', 1.day.ago) + } searchable do text :description do @@ -194,7 +197,6 @@ def self.sort_localized_short 'Reste' => I18n.t('categories.reste.short') } end - def self.select_sorts Medium.sort_localized.except('RandomQuiz').map { |k, v| [v, k] } end @@ -230,6 +232,7 @@ def self.select_question def self.search_all(params) lecture = Lecture.find_by_id(params[:id]) return Medium.none if lecture.nil? + media_in_project = Medium.media_in_project(params[:project]) # media sitting at course level course_media_in_project = media_in_project.includes(:tags) @@ -244,6 +247,7 @@ def self.search_all(params) # returns the ARel of all media for the given project def self.media_in_project(project) return Medium.none unless project.present? + sort = project == 'keks' ? 'Quiz' : project.capitalize Medium.where(sort: sort) end @@ -263,6 +267,7 @@ def self.search_sorts(search_params) unless search_params[:all_types] == '0' return (Medium.sort_enum - ['RandomQuiz']) end + search_params[:types] || [] end @@ -280,9 +285,10 @@ def self.search_by(search_params, page) search_params[:types] = [] if search_params[:all_types] == '1' search_params[:teachable_ids] = TeachableParser.new(search_params) .teachables_as_strings - search_params[:editor_ids] = [] if search_params[:all_editors] == '1' || search_params[:all_editors].nil? + search_params[:editor_ids] = + [] if search_params[:all_editors] == '1' || search_params[:all_editors].nil? # add media without term to current term - + search_params[:all_terms] = '1' if search_params[:all_terms].blank? search_params[:all_teachers] = '1' if search_params[:all_teachers].blank? search_params[:term_ids].push('0') if search_params[:term_ids].present? @@ -296,8 +302,10 @@ def self.search_by(search_params, page) without(:sort, Medium.advanced_sorts) unless admin with(:editor_ids, search_params[:editor_ids]) with(:teachable_compact, search_params[:teachable_ids]) - with(:term_id, search_params[:term_ids]) unless search_params[:all_terms] == '1' - with(:teacher_id, search_params[:teacher_ids]) unless search_params[:all_teachers] == '1' + with(:term_id, + search_params[:term_ids]) unless search_params[:all_terms] == '1' + with(:teacher_id, + search_params[:teacher_ids]) unless search_params[:all_teachers] == '1' end if search_params[:purpose] == 'clicker' search.build do @@ -315,7 +323,7 @@ def self.search_by(search_params, page) end end unless search_params[:all_tags] == '1' && - search_params[:tag_operator] == 'or' + search_params[:tag_operator] == 'or' if search_params[:tag_ids] if search_params[:tag_operator] == 'or' || search_params[:all_tags] == '1' search.build do @@ -375,6 +383,7 @@ def self.similar_courses(search_string) def restricted? return false unless teachable + teachable.restricted? end @@ -386,12 +395,14 @@ def restricted? # where the user temporarily commented out some part of the manuscript) def protected_items return [] unless sort == 'Script' + pdf_items = Item.where(medium: self).where.not(pdf_destination: nil) Referral.where(item: pdf_items).map(&:item).uniq end def vanished_items return [] unless sort == 'Script' + Item.where(medium: self) .where.not(sort: 'self') .where.not(pdf_destination: manuscript_destinations) @@ -422,6 +433,7 @@ def quarantine # return these) def update_pdf_destinations! return unless sort == 'Script' + irrelevant_items.delete_all result = missing_items_outside_quarantine.pluck(:pdf_destination) missing_items_outside_quarantine.update_all(quarantine: true) @@ -431,6 +443,7 @@ def update_pdf_destinations! # is the given user an editor of this medium? def edited_by?(user) return true if editors.include?(user) + false end @@ -443,18 +456,20 @@ def edited_with_inheritance_by?(user) return true if teachable&.lecture&.teacher == user return true if teachable&.course&.editors&.include?(user) return true if teachable&.is_a?(Talk) && user.in?(teachable.speakers) + false end def editors_with_inheritance return [] if sort == 'RandomQuiz' + result = (editors&.to_a + teachable.lecture&.editors.to_a + [teachable.lecture&.teacher] + teachable.course.editors.to_a).uniq.compact return result unless teachable.is_a?(Talk) + (result + teachable.speakers).uniq end - # creates a .vtt tmp file (and returns it), which contains # all data needed by the thyme player to realize the toc def toc_to_vtt @@ -507,11 +522,13 @@ def referrals_by_time def screenshot_url_with_host return screenshot_url(host: host) unless screenshot(:normalized) + return screenshot_url(:normalized, host: host) end def video_url return unless video.present? + video.url(host: host) end @@ -521,36 +538,43 @@ def video_download_url def video_filename return unless video.present? + video.metadata['filename'] end def video_size return unless video.present? + video.metadata['size'] end def video_resolution return unless video.present? + video.metadata['resolution'] end def video_duration return unless video.present? + video.metadata['duration'] end def video_duration_hms_string return unless video.present? + TimeStamp.new(total_seconds: video_duration).hms_string end def geogebra_filename return unless geogebra.present? + geogebra.metadata['filename'] end def geogebra_size return unless geogebra.present? + geogebra.metadata['size'] end @@ -564,11 +588,13 @@ def geogebra_download_url def geogebra_screenshot_url return '' unless geogebra.present? + geogebra_url(:screenshot, host: host) end def manuscript_url_with_host - return manuscript_url(host: host) + "/"+manuscript_filename if ENV["REWRITE_ENABLED"]=="1" + return manuscript_url(host: host) + "/" + manuscript_filename if ENV["REWRITE_ENABLED"] == "1" + manuscript_url(host: host) end @@ -578,53 +604,62 @@ def manuscript_download_url def manuscript_filename return unless manuscript.present? + return manuscript.metadata['filename'] end def manuscript_size return unless manuscript.present? + return manuscript.metadata['size'] end def manuscript_pages return unless manuscript.present? + return manuscript.metadata['pages'] end - def manuscript_screenshot_url return '' unless manuscript.present? + manuscript_url(:screenshot, host: host) end def manuscript_destinations return [] unless manuscript.present? && sort == 'Script' + manuscript.metadata['destinations'] || [] end def video_width return unless video.present? + video_resolution.split('x')[0].to_i end def video_height return unless video.present? + video_resolution.split('x')[1].to_i end def video_aspect_ratio return unless video_height != 0 && video_width != 0 + video_width.to_f / video_height end def video_scaled_height(new_width) return unless video_height != 0 && video_width != 0 + (new_width.to_f / video_aspect_ratio).to_i end def caption return description if description.present? return '' unless sort == 'Kaviar' && teachable_type == 'Lesson' + teachable.section_titles || '' end @@ -644,6 +679,7 @@ def card_subheader def card_tooltip return Medium.sort_localized[sort] unless sort == 'Nuesse' && file_last_edited + I18n.t('categories.exercises.singular_updated') end @@ -653,6 +689,7 @@ def sort_localized def subheader_style return 'badge badge-secondary' unless sort == 'Nuesse' && file_last_edited + 'badge badge-danger' end @@ -689,6 +726,7 @@ def visible_for_user?(user) return true if edited_with_inheritance_by?(user) return false unless published? return false if locked? + if teachable_type == 'Course' return false if restricted? && !teachable.in?(user.courses) end @@ -700,16 +738,19 @@ def visible_for_user?(user) def course return if teachable.nil? + teachable.course end def lecture return if teachable.nil? + teachable.lecture end def lesson return if teachable.nil? + teachable.lesson end @@ -720,6 +761,7 @@ def irrelevant? def teachable_select return nil unless teachable.present? + teachable_type + '-' + teachable_id.to_s end @@ -733,6 +775,7 @@ def siblings def compact_info_uncached return "#{sort_localized}.#{teachable.compact_title}" unless quizzy? + "#{sort_localized}.#{teachable.compact_title}.\##{id}" end @@ -749,10 +792,11 @@ def compact_info def local_info_uncached return description if description.present? return I18n.t('admin.medium.local_info.no_title') unless undescribable? - if sort == 'Kaviar' && teachable_type == 'Lesson' - return I18n.t('admin.medium.local_info.to_session', - number: teachable.number, - date: teachable.date_localized) + + if sort == 'Kaviar' && teachable_type == 'Lesson' + return I18n.t('admin.medium.local_info.to_session', + number: teachable.number, + date: teachable.date_localized) elsif sort == 'Script' return I18n.t('categories.script.singular') @@ -768,6 +812,7 @@ def local_info def local_info_for_admins_uncached return local_info unless quizzy? + "\##{id}.#{local_info}" end @@ -784,6 +829,7 @@ def details_uncached unless undescribable? return "#{I18n.t('admin.medium.local_info.no_title')}.ID#{id}" end + '' end @@ -795,6 +841,7 @@ def details def title_uncached return compact_info if details.blank? + compact_info + '.' + details end @@ -825,14 +872,14 @@ def scoped_teachable_title # returns info made from sort and description def local_title_for_viewers_uncached - return"#{sort_localized}, #{description}" if description.present? + return "#{sort_localized}, #{description}" if description.present? if sort == 'Kaviar' && teachable.class.to_s == 'Lesson' return "#{I18n.t('categories.kaviar.singular')}, #{teachable.local_title_for_viewers}" end + "#{sort_localized}, #{I18n.t('admin.medium.local_info.no_title')}" end - # returns info made from sort and description def local_title_for_viewers Rails.cache.fetch("#{cache_key_with_version}/local_title_for_viewers") do @@ -868,6 +915,7 @@ def items_with_references def proper? return true unless sort == 'RandomQuiz' + false end @@ -883,14 +931,14 @@ def sanitize_type! def select_sorts result = if new_record? - Medium.sort_localized.except('RandomQuiz') - elsif sort.in?(['Kaviar', 'Sesam', 'Erdbeere', 'Kiwi', 'Nuesse', - 'Reste']) - Medium.sort_localized.except('RandomQuiz', 'Script', 'Quiz', - 'Question', 'Remark') - else - Medium.sort_localized.slice(sort) - end + Medium.sort_localized.except('RandomQuiz') + elsif sort.in?(['Kaviar', 'Sesam', 'Erdbeere', 'Kiwi', 'Nuesse', + 'Reste']) + Medium.sort_localized.except('RandomQuiz', 'Script', 'Quiz', + 'Question', 'Remark') + else + Medium.sort_localized.slice(sort) + end if teachable_type == 'Talk' result.except!('RandomQuiz', 'Question', 'Remark', 'Erdbeere', 'Script') end @@ -924,12 +972,14 @@ def linked_media_ids_cached def toc_items return [] unless sort == 'Script' + items.where(sort: ['chapter', 'section']) .natural_sort_by { |x| [x.page, x.ref_number] } end def tags_outside_lesson return Tag.none unless teachable_type == 'Lesson' + tags.where.not(id: teachable.tag_ids) end @@ -946,16 +996,19 @@ def script_items_importable? return unless teachable_type == 'Lesson' return unless teachable.lecture.content_mode == 'manuscript' return unless teachable.script_items.any? + true end def import_script_items! return unless teachable_type == 'Lesson' return unless teachable.lecture.content_mode == 'manuscript' + items = teachable.script_items return unless items.any? + items.each_with_index.each do |i, j| - Item.create(start_time: TimeStamp.new(h: 0, m:0, s: 0, ms: j), + Item.create(start_time: TimeStamp.new(h: 0, m: 0, s: 0, ms: j), sort: i.sort, description: i.description, medium: self, section: i.section, ref_number: i.ref_number, @@ -972,6 +1025,7 @@ def scoped_teachable def publish! return if published? return unless publisher + success = publisher.publish! update(publisher: nil) if success end @@ -979,22 +1033,26 @@ def publish! def release_date_set? return false unless publisher return false unless publisher.release_date + true end def planned_release_date return unless publisher + publisher.release_date end def planned_comment_lock? return publisher.lock_comments if publisher + !!teachable.media_scope.try(:comments_disabled) end def becomes_quizzable return unless type.in?(['Question', 'Remark']) return becomes(Question) if type == 'Question' + becomes(Remark) end @@ -1038,8 +1096,8 @@ def supervising_teacher_id end def subscribed_users - - return teachable.user_ids if ['Lecture', 'Course'].include? teachable.class.to_s + return teachable.user_ids if ['Lecture', + 'Course'].include? teachable.class.to_s return unless teachable.class.to_s == 'Lesson' Lecture.find_by(id: teachable.lecture_id).user_ids @@ -1047,159 +1105,175 @@ def subscribed_users private - # media of type kaviar associated to a lesson and script do not require - # a description - def undescribable? - (sort == 'Kaviar' && teachable.class.to_s == 'Lesson') || - sort == 'Script' - end + # media of type kaviar associated to a lesson and script do not require + # a description + def undescribable? + (sort == 'Kaviar' && teachable.class.to_s == 'Lesson') || + sort == 'Script' + end - def quizzy? - sort.in?(['Quiz', 'Question', 'Remark']) - end + def quizzy? + sort.in?(['Quiz', 'Question', 'Remark']) + end - def title_uncached - return compact_info if details.blank? - compact_info + '.' + details - end + def title_uncached + return compact_info if details.blank? - def local_title_for_viewers_uncached - return"#{sort_localized}, #{description}" if description.present? - if sort == 'Kaviar' && teachable.class.to_s == 'Lesson' - return "#{I18n.t('categories.kaviar.singular')}, #{teachable.local_title_for_viewers}" + compact_info + '.' + details end - "#{sort_localized}, #{I18n.t('admin.medium.local_info.no_title')}" - end + def local_title_for_viewers_uncached + return "#{sort_localized}, #{description}" if description.present? + if sort == 'Kaviar' && teachable.class.to_s == 'Lesson' + return "#{I18n.t('categories.kaviar.singular')}, #{teachable.local_title_for_viewers}" + end - def touch_teachable - return if teachable.nil? - if teachable.course.present? && teachable.course.persisted? - teachable.course.touch + "#{sort_localized}, #{I18n.t('admin.medium.local_info.no_title')}" end - optional_touches - end - def reset_released_status - return if teachable.nil? || teachable.published? - self.released = nil - end + def touch_teachable + return if teachable.nil? - def optional_touches - if teachable.lecture.present? && teachable.lecture.persisted? - teachable.lecture.touch + if teachable.course.present? && teachable.course.persisted? + teachable.course.touch + end + optional_touches end - if teachable.lesson.present? && teachable.lesson.persisted? - teachable.lesson.touch + + def reset_released_status + return if teachable.nil? || teachable.published? + + self.released = nil end - return unless teachable.talk.present? && teachable.talk.persisted? - teachable.talk.touch - end - def vtt_start - "WEBVTT\n\n" - end + def optional_touches + if teachable.lecture.present? && teachable.lecture.persisted? + teachable.lecture.touch + end + if teachable.lesson.present? && teachable.lesson.persisted? + teachable.lesson.touch + end + return unless teachable.talk.present? && teachable.talk.persisted? - def belongs_to_course?(lecture) - teachable_type == 'Course' && teachable == lecture.course - end + teachable.talk.touch + end - def belongs_to_lecture?(lecture) - teachable_type == 'Lecture' && teachable == lecture - end + def vtt_start + "WEBVTT\n\n" + end - def belongs_to_lesson?(lecture) - teachable_type == 'Lesson' && teachable.lecture == lecture - end + def belongs_to_course?(lecture) + teachable_type == 'Course' && teachable == lecture.course + end - def create_self_item - return if sort.in?(['Question', 'Remark', 'RandomQuiz']) - Item.create(sort: 'self', medium: self) - end + def belongs_to_lecture?(lecture) + teachable_type == 'Lecture' && teachable == lecture + end - def local_items - return teachable.items - items if teachable_type == 'Course' - teachable.lecture.items - items - end + def belongs_to_lesson?(lecture) + teachable_type == 'Lesson' && teachable.lecture == lecture + end + + def create_self_item + return if sort.in?(['Question', 'Remark', 'RandomQuiz']) - def at_most_one_manuscript - return true unless teachable_type == 'Lecture' - return true unless sort == 'Script' - if (Medium.where(sort: 'Script', - teachable: teachable).to_a - [self]).size.positive? - errors.add(:sort, :lecture_manuscript_exists) - return false + Item.create(sort: 'self', medium: self) end - true - end - def script_only_for_lectures - return true if teachable_type == 'Lecture' - return true unless sort == 'Script' - errors.add(:sort, :lecture_only) - false - end + def local_items + return teachable.items - items if teachable_type == 'Course' - def no_video_for_script - return true unless sort == 'Script' - return true unless video.present? - errors.add(:sort, :no_video) - false - end + teachable.lecture.items - items + end + + def at_most_one_manuscript + return true unless teachable_type == 'Lecture' + return true unless sort == 'Script' + + if (Medium.where(sort: 'Script', + teachable: teachable).to_a - [self]).size.positive? + errors.add(:sort, :lecture_manuscript_exists) + return false + end + true + end + + def script_only_for_lectures + return true if teachable_type == 'Lecture' + return true unless sort == 'Script' - def no_changing_sort_to_or_from_script - if sort_was == 'Script' && sort != 'Script' - errors.add(:sort, :no_conversion_from_script) - return false + errors.add(:sort, :lecture_only) + false end - if persisted? && sort_was != 'Script' && sort == 'Script' - errors.add(:sort, :no_conversion_to_script) - return false + + def no_video_for_script + return true unless sort == 'Script' + return true unless video.present? + + errors.add(:sort, :no_video) + false end - true - end - def no_tags_for_scripts - return true unless sort == 'Script' && tags.any? - errors.add(:tags, :no_tags_allowed) - false - end + def no_changing_sort_to_or_from_script + if sort_was == 'Script' && sort != 'Script' + errors.add(:sort, :no_conversion_from_script) + return false + end + if persisted? && sort_was != 'Script' && sort == 'Script' + errors.add(:sort, :no_conversion_to_script) + return false + end + true + end - def delete_vertices - return unless type.in?(['Question', 'Remark']) - if type == 'Question' - becomes(Question).delete_vertices - return + def no_tags_for_scripts + return true unless sort == 'Script' && tags.any? + + errors.add(:tags, :no_tags_allowed) + false end - becomes(Remark).delete_vertices - end - def delete_answers - return unless type == 'Question' - becomes(Question).answers.delete_all - end + def delete_vertices + return unless type.in?(['Question', 'Remark']) - def text_join - return unless type.in?(['Question', 'Remark']) - return text if type == 'Remark' - "#{text} #{becomes(Question).answers&.map(&:text_join)&.join(' ')}" - end + if type == 'Question' + becomes(Question).delete_vertices + return + end + becomes(Remark).delete_vertices + end - def release_state - return released unless released.nil? - 'unpublished' - end + def delete_answers + return unless type == 'Question' - def clickerizable? - return false unless type == 'Question' - question = becomes(Question) - return false unless question.answers.count.in?((2..6)) - question.answers.pluck(:value).count(true) == 1 - end + becomes(Question).answers.delete_all + end - def answers_count - return -1 unless type == 'Question' - becomes(Question).answers.count - end - + def text_join + return unless type.in?(['Question', 'Remark']) + return text if type == 'Remark' + + "#{text} #{becomes(Question).answers&.map(&:text_join)&.join(' ')}" + end + + def release_state + return released unless released.nil? + + 'unpublished' + end + + def clickerizable? + return false unless type == 'Question' + + question = becomes(Question) + return false unless question.answers.count.in?((2..6)) + + question.answers.pluck(:value).count(true) == 1 + end + + def answers_count + return -1 unless type == 'Question' + + becomes(Question).answers.count + end end diff --git a/app/models/medium_publisher.rb b/app/models/medium_publisher.rb index b1d2ffb5a..1d5923632 100644 --- a/app/models/medium_publisher.rb +++ b/app/models/medium_publisher.rb @@ -91,6 +91,7 @@ def publish! def assignment return unless @create_assignment + Assignment.new(lecture: medium.teachable, medium_id: @medium_id, title: @assignment_title, diff --git a/app/models/notification.rb b/app/models/notification.rb index e155942c6..278066c89 100644 --- a/app/models/notification.rb +++ b/app/models/notification.rb @@ -9,12 +9,12 @@ class Notification < ApplicationRecord paginates_per 12 -# retrieve notifiable defined by notifiable_type and notifiable_id -# def notifiable -# return unless notifiable_type.in?(Notification.allowed_notifiable_types) && -# notifiable_id.present? -# notifiable_type.constantize.find_by_id(notifiable_id) -# end + # retrieve notifiable defined by notifiable_type and notifiable_id + # def notifiable + # return unless notifiable_type.in?(Notification.allowed_notifiable_types) && + # notifiable_id.present? + # notifiable_type.constantize.find_by_id(notifiable_id) + # end # returns the lecture associated to a notification of type announcement, # and teachable for a notification of type medium, nil otherwise @@ -22,6 +22,7 @@ def teachable return unless notifiable.present? return if notifiable_type.in?(['Lecture', 'Course']) return notifiable.lecture if notifiable_type == 'Announcement' + # notifiable will be a medium, so return its teachable notifiable.teachable end @@ -34,13 +35,16 @@ def teachable def path(user) return unless notifiable.present? return edit_profile_path if notifiable_type.in?(['Course', 'Lecture']) + if notifiable_type == 'Announcement' return notifiable.lecture.path(user) if notifiable.lecture.present? + return news_path end if notifiable_type == 'Medium' && notifiable.sort == 'Quiz' return medium_path(notifiable) end + polymorphic_url(notifiable, only_path: true) end @@ -52,21 +56,25 @@ def self.allowed_notifiable_types def medium? return unless notifiable.present? + notifiable_type == 'Medium' end def course? return unless notifiable.present? + notifiable.class.to_s == 'Course' end def lecture? return unless notifiable.present? + notifiable.class.to_s == 'Lecture' end def announcement? return unless notifiable.present? + notifiable.class.to_s == 'Announcement' end diff --git a/app/models/notion.rb b/app/models/notion.rb index 78df58c93..de98a93d9 100644 --- a/app/models/notion.rb +++ b/app/models/notion.rb @@ -11,6 +11,7 @@ class Notion < ApplicationRecord def presence_of_tag return if tag || aliased_tag + errors.add(:tag, :no_tag) end @@ -26,9 +27,9 @@ def touch_tag_relations private - def clear_tag_cache - I18n.available_locales.each do |l| - Rails.cache.delete("tag_select_by_title_#{l}") + def clear_tag_cache + I18n.available_locales.each do |l| + Rails.cache.delete("tag_select_by_title_#{l}") + end end - end end diff --git a/app/models/probe.rb b/app/models/probe.rb index 6ac6921ec..59b892754 100644 --- a/app/models/probe.rb +++ b/app/models/probe.rb @@ -1,5 +1,7 @@ class Probe < InteractionsRecord - scope :created_between, lambda {|start_date, end_date| where(created_at: start_date.beginning_of_day..end_date.end_of_day)} + scope :created_between, lambda { |start_date, end_date| + where(created_at: start_date.beginning_of_day..end_date.end_of_day) + } require 'csv' def self.finished_quizzes(quiz) @@ -35,10 +37,10 @@ def self.local_success_in_quiz(quiz) rel_incorrect = total.zero? ? nil : 100 - rel_correct results[q] = { correct: probes.where(question_id: q, correct: true).count, - incorrect: probes.where(question_id: q, - correct: false).count, - rel_correct: rel_correct, - rel_incorrect: rel_incorrect } + incorrect: probes.where(question_id: q, + correct: false).count, + rel_correct: rel_correct, + rel_incorrect: rel_incorrect } end results end @@ -51,9 +53,8 @@ def self.to_csv csv << attributes all.each do |probe| - csv << attributes.map{ |attr| probe.send(attr) } + csv << attributes.map { |attr| probe.send(attr) } end end end - end diff --git a/app/models/program.rb b/app/models/program.rb index 9a2ee1312..1815a27a6 100644 --- a/app/models/program.rb +++ b/app/models/program.rb @@ -1,22 +1,22 @@ class Program < ApplicationRecord - belongs_to :subject - has_many :divisions, dependent: :destroy + belongs_to :subject + has_many :divisions, dependent: :destroy - translates :name - globalize_accessors locales: I18n.available_locales, - attributes: translated_attribute_names + translates :name + globalize_accessors locales: I18n.available_locales, + attributes: translated_attribute_names - def name_with_subject - "#{subject.name}: #{name}" - end + def name_with_subject + "#{subject.name}: #{name}" + end - def courses - divisions.map(&:courses).flatten - end + def courses + divisions.map(&:courses).flatten + end # array of all programs together with their ids for use in options_for_select def self.select_programs Program.includes(:subject).all.sort_by(&:name_with_subject) - .map { |p| [p.name_with_subject, p.id] } + .map { |p| [p.name_with_subject, p.id] } end end diff --git a/app/models/question.rb b/app/models/question.rb index 8dd7c80a9..8d3eb7535 100644 --- a/app/models/question.rb +++ b/app/models/question.rb @@ -57,6 +57,7 @@ def self.create_prefilled(label, teachable, editors) question_sort: 'mc', solution: solution) return question if question.invalid? + Answer.create(question: question, text: '0', value: true) @@ -98,6 +99,7 @@ def parsed_text_with_params def text_with_sample_params(parameters) return text unless parameters.present? + result = text parameters.keys.each do |p| result.gsub!(/\\para{#{Regexp.escape(p)},(.*?)}/, parameters[p].to_s) @@ -121,14 +123,14 @@ def self.parameters_from_text(text) private - def prelim_answer_table - table = [] - size = answer_ids.count - (0..2**size - 1).each do |i| - hash = {} - i.to_bool_a(size).each_with_index.map { |x, j| hash[answer_ids[j]] = x } - table.push(hash) + def prelim_answer_table + table = [] + size = answer_ids.count + (0..2**size - 1).each do |i| + hash = {} + i.to_bool_a(size).each_with_index.map { |x, j| hash[answer_ids[j]] = x } + table.push(hash) + end + table end - table - end end diff --git a/app/models/quiz.rb b/app/models/quiz.rb index 5824383f2..bd7871991 100644 --- a/app/models/quiz.rb +++ b/app/models/quiz.rb @@ -17,11 +17,13 @@ def label def publish_vertices!(user, release_state) return unless vertices + vertices.keys.each do |v| quizzable = quizzable(v) next if quizzable.published? next if !quizzable.teachable.published? next unless user.in?(quizzable.editors_with_inheritance) || user.admin + quizzable.update(released: release_state, released_at: Time.now) end end @@ -40,6 +42,7 @@ def quizzables_visible_for_user?(user) def next_vertex(progress, input = {}) return default_table[progress] if vertices[progress][:type] == 'Remark' + target_vertex(progress, input) end @@ -50,32 +53,38 @@ def replace_reference!(old_quizzable, new_quizzable, answer_map = {}) def vertices return if quiz_graph.nil? + quiz_graph.vertices end def edges return if quiz_graph.nil? + quiz_graph.edges end def root return if quiz_graph.nil? + quiz_graph.root end def default_table return if quiz_graph.nil? + quiz_graph.default_table end def question_ids return [] unless quiz_graph && quiz_graph.vertices.present? + quiz_graph.vertices.select { |_k, v| v[:type] == 'Question' } .values.map { |v| v[:id] }.uniq end def remark_ids return [] unless quiz_graph && quiz_graph.vertices.present? + quiz_graph.vertices.select { |_k, v| v[:type] == 'Remark' }.values .map { |v| v[:id] }.uniq end @@ -98,6 +107,7 @@ def quizzable(vertex_id) def preselected_branch(vertex_id, crosses, fallback) successor = next_vertex(vertex_id, crosses) return successor unless successor == default_table[vertex_id] && fallback + 0 end @@ -113,6 +123,7 @@ def questions def questions_count return 0 unless quiz_graph + quiz_graph.questions_count end @@ -124,8 +135,8 @@ def find_errors private - def target_vertex(progress, input) - edges.select { |e, t| e[0] == progress && t.include?(input) }&.keys - &.first&.second || default_table[progress] - end + def target_vertex(progress, input) + edges.select { |e, t| e[0] == progress && t.include?(input) }&.keys + &.first&.second || default_table[progress] + end end diff --git a/app/models/quiz_certificate.rb b/app/models/quiz_certificate.rb index 2df1ce34e..d08069209 100644 --- a/app/models/quiz_certificate.rb +++ b/app/models/quiz_certificate.rb @@ -13,7 +13,7 @@ def self.generate_code private - def set_code - self.code = QuizCertificate.generate_code - end + def set_code + self.code = QuizCertificate.generate_code + end end diff --git a/app/models/quiz_graph.rb b/app/models/quiz_graph.rb index 6711d0a6b..4688aa013 100644 --- a/app/models/quiz_graph.rb +++ b/app/models/quiz_graph.rb @@ -18,6 +18,7 @@ def self.dump(quiz_graph) def update_vertex(vertex_id, branching, hide) return if @vertices[vertex_id][:type] == 'Remark' + remove_edges_from!(vertex_id) update_edges_for_question!(vertex_id, branching) update_hide_solutions!(vertex_id, hide) @@ -27,6 +28,7 @@ def update_vertex(vertex_id, branching, hide) def create_vertex(quizzable) return self if quizzable.blank? return self if quizzable.invalid? + id = new_vertex_id @vertices[id] = { type: quizzable.class.to_s, id: quizzable.id } @default_table[id] = 0 @@ -50,8 +52,9 @@ def delete_edge!(source, target) if @default_table[source] == target @default_table[source] = 0 end - answers = @edges[[source,target]] + answers = @edges[[source, target]] return unless answers + @edges.delete([source, target]) affected_hide_solution = @hide_solution.select do |h| h.first == source && h.second.in?(answers) @@ -68,9 +71,11 @@ def delete_edge!(source, target) def replace_reference!(old_quizzable, new_quizzable, answer_map = {}) return self unless old_quizzable.class == new_quizzable.class + affected_vertices = referencing_vertices(old_quizzable) affected_vertices.each { |v| @vertices[v][:id] = new_quizzable.id } return self unless new_quizzable.class.to_s == 'Question' + affected_vertices.each do |v| bend_edges_rereferencing!(edges_from(v), answer_map) bend_hide_solution_rereferencing!(v, answer_map) @@ -86,8 +91,11 @@ def reset_vertex_answers_change(id) def find_errors return [I18n.t('admin.quiz.no_vertices')] unless @vertices.present? + branch_undef = @default_table.values.include?(0) - no_end = default_table.values.exclude?(-1) && @edges.select { |e| e[1] == -1 }.blank? + no_end = default_table.values.exclude?(-1) && @edges.select { |e| + e[1] == -1 + }.blank? no_root = @root.blank? || @root.zero? messages = [] messages.push(I18n.t('admin.quiz.undefined_targets')) if branch_undef @@ -102,6 +110,7 @@ def warnings def quizzable(id) return unless id.in?(@vertices.keys) + @vertices[id][:type].constantize.find_by_id(@vertices[id][:id]) end @@ -203,6 +212,7 @@ def referencing_vertices(quizzable) def new_vertex_id return 1 unless @vertices.present? + @vertices.keys.max + 1 end @@ -214,6 +224,7 @@ def border_color_for_cytoscape(id) quizzable = quizzable(id) return 'orange' unless quizzable.visible? return 'chocolate' if quizzable.restricted? + '#222' end @@ -234,7 +245,7 @@ def self.build_from_questions(question_ids) edges = {} default_table = {} size = question_ids.size - question_ids.each_with_index do |q,i| + question_ids.each_with_index do |q, i| j = i + 1 k = j < size ? j + 1 : -1 question = Question.find_by_id(q) @@ -253,7 +264,7 @@ def to_cytoscape background: 'yellowgreen', borderwidth: '0', bordercolor: 'grey', - shape: 'diamond' } ) + shape: 'diamond' }) # add vertices @vertices.keys.each do |v| result.push(data: cytoscape_vertex(v)) @@ -264,13 +275,13 @@ def to_cytoscape background: 'yellowgreen', borderwidth: '0', bordercolor: '#f4a460', - shape: 'diamond' } ) + shape: 'diamond' }) # add edges if @root.in?(@vertices.keys) result.push(data: { id: "-2-#{@root}", source: -2, target: @root, - color: '#aaa'} ) + color: '#aaa' }) end @vertices.keys.each do |v| edges_from_plus_default(v).each do |e| @@ -306,11 +317,12 @@ def cytoscape_edge(edge) end def questions_count - @vertices.values.select { |v| v[:type] == 'Question'}.count + @vertices.values.select { |v| v[:type] == 'Question' }.count end def default?(edge) return false unless @default_table[edge.first] + @default_table[edge.first] == edge.second end -end \ No newline at end of file +end diff --git a/app/models/quiz_round.rb b/app/models/quiz_round.rb index fc9b1322f..17ec833f6 100644 --- a/app/models/quiz_round.rb +++ b/app/models/quiz_round.rb @@ -52,6 +52,7 @@ def round_id def background return 'bg-grey-lighten-4' if @hide_solution return 'bg-correct' if @correct + 'bg-incorrect' end @@ -61,75 +62,81 @@ def badge def statement return I18n.t('admin.quiz.correct_result') if @correct + I18n.t('admin.quiz.incorrect_result') end def answers return [] unless @answer_shuffle + @answer_shuffle.map { |a| Answer.find_by_id(a) } end def answers_old return [] unless @answer_shuffle_old + @answer_shuffle_old.map { |a| Answer.find_by_id(a) } end private - def progress_counter(params) - if params[:quiz].present? - @counter = params[:quiz][:counter].to_i - @progress = params[:quiz][:progress].to_i - @session_id = params[:quiz][:session_id] + def progress_counter(params) + if params[:quiz].present? + @counter = params[:quiz][:counter].to_i + @progress = params[:quiz][:progress].to_i + @session_id = params[:quiz][:session_id] + end + @progress ||= @quiz.root + @counter ||= 0 + @session_id ||= SecureRandom.uuid.first(13).remove('-') + @progress_old = @progress + @counter_old = @counter + @round_id_old = round_id end - @progress ||= @quiz.root - @counter ||= 0 - @session_id ||= SecureRandom.uuid.first(13).remove('-') - @progress_old = @progress - @counter_old = @counter - @round_id_old = round_id - end - def question_details(params) - @is_question = true - @question_id = @vertex[:id] - @answer_scheme = Question.find(@question_id).answer_scheme - if params[:quiz].present? && params[:quiz][:answer_shuffle].present? - @answer_shuffle = JSON.parse(params[:quiz][:answer_shuffle]).map(&:to_i) - else - @answer_shuffle = Question.find(@question_id).answers.map(&:id).shuffle + def question_details(params) + @is_question = true + @question_id = @vertex[:id] + @answer_scheme = Question.find(@question_id).answer_scheme + if params[:quiz].present? && params[:quiz][:answer_shuffle].present? + @answer_shuffle = JSON.parse(params[:quiz][:answer_shuffle]).map(&:to_i) + else + @answer_shuffle = Question.find(@question_id).answers.map(&:id).shuffle + end end - end - def remark_details(params) - @is_remark = true - @remark_id = @vertex[:id] - end + def remark_details(params) + @is_remark = true + @remark_id = @vertex[:id] + end - def update_answer_shuffle - @answer_shuffle = Question.find_by_id(@vertex[:id])&.answers&.map(&:id) - &.shuffle - end + def update_answer_shuffle + @answer_shuffle = Question.find_by_id(@vertex[:id])&.answers&.map(&:id) + &.shuffle + end - def create_question_probe - return unless @save_probe - quiz_id = @quiz.id unless @quiz.sort == 'RandomQuiz' - input = @solution_input || @input.to_s if @study_participant - ProbeSaver.perform_async(quiz_id, @question_id, nil, @correct, @progress, - @session_id, @study_participant, input) - end + def create_question_probe + return unless @save_probe - def create_remark_probe - return unless @save_probe - quiz_id = @quiz.id unless @quiz.sort == 'RandomQuiz' - ProbeSaver.perform_async(quiz_id, nil, @remark_id, nil, @progress, - @session_id, @study_participant, @input_text) - end + quiz_id = @quiz.id unless @quiz.sort == 'RandomQuiz' + input = @solution_input || @input.to_s if @study_participant + ProbeSaver.perform_async(quiz_id, @question_id, nil, @correct, @progress, + @session_id, @study_participant, input) + end - def create_certificate_final_probe - return unless @save_probe - @certificate = QuizCertificate.create(quiz: @quiz) - ProbeSaver.perform_async(@quiz.id, nil, nil, nil, -1, @session_id, - @study_participant, nil) - end + def create_remark_probe + return unless @save_probe + + quiz_id = @quiz.id unless @quiz.sort == 'RandomQuiz' + ProbeSaver.perform_async(quiz_id, nil, @remark_id, nil, @progress, + @session_id, @study_participant, @input_text) + end + + def create_certificate_final_probe + return unless @save_probe + + @certificate = QuizCertificate.create(quiz: @quiz) + ProbeSaver.perform_async(@quiz.id, nil, nil, nil, -1, @session_id, + @study_participant, nil) + end end diff --git a/app/models/reader.rb b/app/models/reader.rb index a49c6c407..0ba906da7 100644 --- a/app/models/reader.rb +++ b/app/models/reader.rb @@ -2,6 +2,6 @@ # Reader keeps track of when a user hast last seen a certain thread, # making it possible to display whether there are new comments class Reader < ApplicationRecord - belongs_to :user - belongs_to :thread, class_name: 'Commontator::Thread' + belongs_to :user + belongs_to :thread, class_name: 'Commontator::Thread' end diff --git a/app/models/referral.rb b/app/models/referral.rb index 8040a6083..ecf3e6f32 100644 --- a/app/models/referral.rb +++ b/app/models/referral.rb @@ -22,6 +22,7 @@ class Referral < ApplicationRecord # otherwise, return the item's explanation def explain return explanation if item.nil? + explanation || item.explanation end @@ -104,51 +105,56 @@ def item_in_quarantine? private - # explanation provided to the vtt file - # returns the referral's explanation if present, the item's explanation - # if the item is a link, otherwise nil - def vtt_explanation - return explanation if explanation.present? - return item.explanation if item.sort == 'link' && item.explanation.present? - end + # explanation provided to the vtt file + # returns the referral's explanation if present, the item's explanation + # if the item is a link, otherwise nil + def vtt_explanation + return explanation if explanation.present? + return item.explanation if item.sort == 'link' && item.explanation.present? + end - # some method that check for valid start and end time + # some method that check for valid start and end time - def valid_start_time - return true if start_time.nil? - return true if start_time.valid? - errors.add(:start_time, :invalid_format) - false - end + def valid_start_time + return true if start_time.nil? + return true if start_time.valid? - def valid_end_time - return true if end_time.nil? - return true if end_time.valid? - errors.add(:end_time, :invalid_format) - false - end + errors.add(:start_time, :invalid_format) + false + end - def start_time_not_too_late - return true if medium.nil? || !medium.video - return true unless start_time.valid? - return true if start_time.total_seconds <= medium.video_duration - errors.add(:start_time, :too_late) - false - end + def valid_end_time + return true if end_time.nil? + return true if end_time.valid? - def end_time_not_too_late - return true if medium.nil? || !medium.video - return true unless end_time.valid? - return true if end_time.total_seconds <= medium.video_duration - errors.add(:end_time, :too_late) - false - end + errors.add(:end_time, :invalid_format) + false + end - def end_time_not_too_soon - return true unless start_time&.valid? - return true unless end_time&.valid? - return true if start_time.total_seconds < end_time.total_seconds - errors.add(:end_time, :too_soon) - false - end + def start_time_not_too_late + return true if medium.nil? || !medium.video + return true unless start_time.valid? + return true if start_time.total_seconds <= medium.video_duration + + errors.add(:start_time, :too_late) + false + end + + def end_time_not_too_late + return true if medium.nil? || !medium.video + return true unless end_time.valid? + return true if end_time.total_seconds <= medium.video_duration + + errors.add(:end_time, :too_late) + false + end + + def end_time_not_too_soon + return true unless start_time&.valid? + return true unless end_time&.valid? + return true if start_time.total_seconds < end_time.total_seconds + + errors.add(:end_time, :too_soon) + false + end end diff --git a/app/models/relation.rb b/app/models/relation.rb index 533eada78..a3775e48e 100644 --- a/app/models/relation.rb +++ b/app/models/relation.rb @@ -13,32 +13,33 @@ class Relation < ApplicationRecord private - def create_inverse - self.class.create(inverse_relation_options) - end - - def destroy_inverses - inverses.destroy_all - end - - def self_inverse? - tag_id == related_tag_id - end - - def inverse? - self.class.exists?(inverse_relation_options) || self_inverse? - end - - def inverses - self.class.where(inverse_relation_options) - end - - def inverse_relation_options - { related_tag_id: tag_id, tag_id: related_tag_id } - end - - def touch_tag - return if tag.nil? - tag.touch - end + def create_inverse + self.class.create(inverse_relation_options) + end + + def destroy_inverses + inverses.destroy_all + end + + def self_inverse? + tag_id == related_tag_id + end + + def inverse? + self.class.exists?(inverse_relation_options) || self_inverse? + end + + def inverses + self.class.where(inverse_relation_options) + end + + def inverse_relation_options + { related_tag_id: tag_id, tag_id: related_tag_id } + end + + def touch_tag + return if tag.nil? + + tag.touch + end end diff --git a/app/models/section.rb b/app/models/section.rb index ff4e30f9c..e6287dcfe 100644 --- a/app/models/section.rb +++ b/app/models/section.rb @@ -15,7 +15,7 @@ class Section < ApplicationRecord # a section has many lessons has_many :lesson_section_joins, dependent: :destroy has_many :lessons, -> { order(date: :asc, id: :asc) }, - through: :lesson_section_joins + through: :lesson_section_joins # a section needs to have a title validates :title, presence: true @@ -42,6 +42,7 @@ def lecture def reference_number return calculated_number unless display_number.present? + display_number end @@ -61,11 +62,13 @@ def reference def calculated_number return relative_position unless lecture.absolute_numbering return absolute_position.to_s unless lecture.start_section.present? + (absolute_position + lecture.start_section - 1).to_s end def to_label return displayed_number + '. ' + title unless hidden_with_inheritance? + '*' + displayed_number + '. ' + title end @@ -75,7 +78,7 @@ def media lessons.map(&:media).flatten end - # visible media are published with inheritance and unlocked + # visible media are published with inheritance and unlocked def visible_media_for_user(user) media.select { |m| m.visible_for_user?(user) } end @@ -85,6 +88,7 @@ def visible_for_user?(user) return true if lecture.edited_by?(user) return false unless lecture.published? return false unless lecture.visible_for_user?(user) + true end @@ -92,8 +96,10 @@ def previous_preliminary return higher_item unless first? return if chapter.first? return unless previous_chapter + potential_last = previous_chapter.sections.last return potential_last if potential_last.last? + potential_last.lower_items.last end @@ -112,8 +118,10 @@ def next_preliminary return lower_item unless last? return if chapter.last? return unless next_chapter + potential_first = next_chapter.sections.first return potential_first if potential_first.first? + potential_first.higher_items.first end @@ -150,6 +158,7 @@ def visible_items_by_time def visible_items return visible_items_by_time if lecture.content_mode == 'video' + script_items_by_position end @@ -166,48 +175,51 @@ def duplicate_in_chapter(new_chapter, import_tags) new_section.chapter = new_chapter new_section.save return unless import_tags + new_section.update(tags_order: tags_order) new_section.tags << tags end private - def touch_lecture - return unless lecture.present? && lecture.persisted? - lecture.touch - end + def touch_lecture + return unless lecture.present? && lecture.persisted? - def touch_media - lecture.media_with_inheritance.update_all(updated_at: Time.current) - touch - end + lecture.touch + end - def touch_self - touch - end + def touch_media + lecture.media_with_inheritance.update_all(updated_at: Time.current) + touch + end - def touch_toc - return unless lecture.absolute_numbering - lecture.chapters.update_all(updated_at: Time.now) - lecture.sections.update_all(updated_at: Time.now) - end + def touch_self + touch + end - def relative_position - chapter.displayed_number + '.' + position.to_s - end + def touch_toc + return unless lecture.absolute_numbering - def absolute_position - chapter.higher_items.includes(:sections).map(&:sections).flatten.size + - position - end + lecture.chapters.update_all(updated_at: Time.now) + lecture.sections.update_all(updated_at: Time.now) + end - def next_chapter - # actual next chapter may not have any sections - chapter.lower_items.find { |c| c.sections.exists? } - end + def relative_position + chapter.displayed_number + '.' + position.to_s + end - def previous_chapter - # actual previous chapter may not have any sections - chapter.higher_items.find { |c| c.sections.exists? } - end + def absolute_position + chapter.higher_items.includes(:sections).map(&:sections).flatten.size + + position + end + + def next_chapter + # actual next chapter may not have any sections + chapter.lower_items.find { |c| c.sections.exists? } + end + + def previous_chapter + # actual previous chapter may not have any sections + chapter.higher_items.find { |c| c.sections.exists? } + end end diff --git a/app/models/section_tag_join.rb b/app/models/section_tag_join.rb index a8ccf4477..24055766b 100644 --- a/app/models/section_tag_join.rb +++ b/app/models/section_tag_join.rb @@ -1,7 +1,7 @@ # SectionTagJoin class # Join table for section<->tag many-to-many-relation class SectionTagJoin < ApplicationRecord - default_scope { order :tag_position } + default_scope { order :tag_position } belongs_to :section belongs_to :tag acts_as_list scope: :section, top_of_list: 0, column: :tag_position diff --git a/app/models/solution.rb b/app/models/solution.rb index 2d108888c..010ca8f2d 100644 --- a/app/models/solution.rb +++ b/app/models/solution.rb @@ -33,19 +33,22 @@ def nerd def tex return '' unless @content.tex + '$$' + @content.tex + '$$' end def tex_mc_answer return '' unless @content.tex + '$' + @content.tex + '$' end def self.from_hash(solution_type, content) return unless solution_type.in?(['MampfExpression', 'MampfMatrix', 'MampfTuple', 'MampfSet']) + solution = Solution.new(solution_type.constantize.from_hash(content)) solution.explanation = content[:explanation] solution end -end \ No newline at end of file +end diff --git a/app/models/subject.rb b/app/models/subject.rb index 063b651a7..110504013 100644 --- a/app/models/subject.rb +++ b/app/models/subject.rb @@ -1,9 +1,9 @@ class Subject < ApplicationRecord - has_many :programs + has_many :programs - translates :name - globalize_accessors locales: I18n.available_locales, - attributes: translated_attribute_names + translates :name + globalize_accessors locales: I18n.available_locales, + attributes: translated_attribute_names def deletable? programs.none? diff --git a/app/models/submission.rb b/app/models/submission.rb index 11d0e5fc8..8b352da41 100644 --- a/app/models/submission.rb +++ b/app/models/submission.rb @@ -18,50 +18,56 @@ class Submission < ApplicationRecord def partners_of_user(user) return unless user.in?(users) + users - [user] end def team - users.map(&:tutorial_name).natural_sort.join(', ') + users.map(&:tutorial_name).natural_sort.join(', ') end def manuscript_filename return unless manuscript.present? + manuscript.metadata['filename'] end def manuscript_size return unless manuscript.present? + manuscript.metadata['size'] end def manuscript_mime_type - return unless manuscript.present? + return unless manuscript.present? + manuscript.metadata['mime_type'] end def correction_filename return unless correction.present? + correction.metadata['filename'] end def correction_mime_type - return unless correction.present? + return unless correction.present? + correction.metadata['mime_type'] end def correction_size return unless correction.present? + correction.metadata['size'] end - def preceding_tutorial(user) assignment.previous&.map { |a| a.tutorial(user) }&.compact&.first end def invited_users - User.where(id: invited_user_ids) + User.where(id: invited_user_ids) end def self.generate_token @@ -72,7 +78,7 @@ def self.generate_token end def admissible_invitees(user) - user.submission_partners(assignment.lecture) - users + user.submission_partners(assignment.lecture) - users end def in_time? @@ -85,32 +91,33 @@ def too_late? def not_updatable? return false if assignment.active? + assignment.totally_expired? || correction.present? || accepted == false end - #def file_path(downloadable) + # def file_path(downloadable) # return unless manuscript # manuscript.to_io.path # end - #def correction_file_path + # def correction_file_path # return unless correction # correction.to_io.path - #end - - def filename_for_bulk_download(end_of_file='') - (team.first(180) + '-' + - last_modification_by_users_at.strftime("%F-%H%M") + - (too_late? ? '-LATE' : '') + - + '-ID-' + id + end_of_file + - assignment.accepted_file_type) - .gsub(/[\x00\/\\:\*\?\"<>\|]/, '_') - .gsub(/^.*(\\|\/)/, '') - # Strip out the non-ascii characters - .gsub(/[^0-9A-Za-z.\-]/, '_') + # end + + def filename_for_bulk_download(end_of_file = '') + (team.first(180) + '-' + + last_modification_by_users_at.strftime("%F-%H%M") + + (too_late? ? '-LATE' : '') + + + '-ID-' + id + end_of_file + + assignment.accepted_file_type) + .gsub(/[\x00\/\\:\*\?\"<>\|]/, '_') + .gsub(/^.*(\\|\/)/, '') + # Strip out the non-ascii characters + .gsub(/[^0-9A-Za-z.\-]/, '_') end - def self.zip(submissions, downloadables, end_of_file='') + def self.zip(submissions, downloadables, end_of_file = '') begin archived_filestream = Zip::OutputStream.write_buffer do |stream| submissions.zip(downloadables).each do |s, d| @@ -128,85 +135,91 @@ def self.zip(submissions, downloadables, end_of_file='') def self.zip_submissions!(tutorial, assignment) submissions = Submission.where(tutorial: tutorial, assignment: assignment).proper - manuscripts = submissions.collect { |s| s.manuscript if s.manuscript.present? } + manuscripts = submissions.collect { |s| + s.manuscript if s.manuscript.present? + } zip(submissions, manuscripts) end def self.zip_corrections!(tutorial, assignment) submissions = Submission.where(tutorial: tutorial, assignment: assignment).proper - corrections = submissions.collect { |s| s.correction if s.correction.present? } + corrections = submissions.collect { |s| + s.correction if s.correction.present? + } zip(submissions, corrections, '-correction') end - ### # Checks size and if filetype is acceptable ### def check_file_properties_any(metadata, sort) errors = [] - if sort == :submission && metadata['size'] > 10*1024*1024 + if sort == :submission && metadata['size'] > 10 * 1024 * 1024 errors.push I18n.t('submission.manuscript_size_too_big', max_size: '10 MB') end - if sort == :correction && metadata['size'] > 15*1024*1024 + if sort == :correction && metadata['size'] > 15 * 1024 * 1024 errors.push I18n.t('submission.manuscript_size_too_big', max_size: '15 MB') end file_name = metadata['filename'] file_type = File.extname(file_name) - if !file_type.in?(['.cc', '.hh', '.m',".mlx", '.pdf', '.zip',".txt"]) + if !file_type.in?(['.cc', '.hh', '.m', ".mlx", '.pdf', '.zip', ".txt"]) errors.push I18n.t('submission.wrong_file_type', file_type: file_type, accepted_file_type: assignment.accepted_file_type) end return {} unless errors.present? + { sort => errors } end + def check_file_properties(metadata, sort) errors = [] - if sort == :submission && metadata['size'] > 10*1024*1024 + if sort == :submission && metadata['size'] > 10 * 1024 * 1024 errors.push I18n.t('submission.manuscript_size_too_big', max_size: '10 MB') end - if sort == :correction && metadata['size'] > 15*1024*1024 + if sort == :correction && metadata['size'] > 15 * 1024 * 1024 errors.push I18n.t('submission.manuscript_size_too_big', max_size: '15 MB') end file_name = metadata['filename'] file_type = File.extname(file_name) if file_type != assignment.accepted_file_type && - assignment.accepted_file_type != '.tar.gz' + assignment.accepted_file_type != '.tar.gz' errors.push I18n.t('submission.wrong_file_type', file_type: file_type, accepted_file_type: assignment.accepted_file_type) end if assignment.accepted_file_type == '.tar.gz' - if file_type == '.gz' - reduced_type = File.extname(File.basename(file_name, '.gz')) - if reduced_type != '.tar' - errors.push I18n.t('submission.wrong_file_type', - file_type: '.gz', - accepted_file_type: '.tar.gz') + if file_type == '.gz' + reduced_type = File.extname(File.basename(file_name, '.gz')) + if reduced_type != '.tar' + errors.push I18n.t('submission.wrong_file_type', + file_type: '.gz', + accepted_file_type: '.tar.gz') end else - errors.push I18n.t('submission.wrong_file_type', - file_type: file_type, - accepted_file_type: '.tar.gz') - end + errors.push I18n.t('submission.wrong_file_type', + file_type: file_type, + accepted_file_type: '.tar.gz') + end end if (!assignment.accepted_file_type.in?(['.cc', '.hh', '.m']) && !metadata['mime_type'].in?(assignment.accepted_mime_types)) || - (assignment.accepted_file_type.in?(['.cc', '.hh', '.m']) && - (!metadata['mime_type'].starts_with?('text/') && - metadata['mime_type'] != 'application/octet-stream')) + (assignment.accepted_file_type.in?(['.cc', '.hh', '.m']) && + (!metadata['mime_type'].starts_with?('text/') && + metadata['mime_type'] != 'application/octet-stream')) errors.push I18n.t('submission.wrong_mime_type', - mime_type: metadata['mime_type'], - accepted_mime_types: assignment.accepted_mime_types - .join(', ')) + mime_type: metadata['mime_type'], + accepted_mime_types: assignment.accepted_mime_types + .join(', ')) end return {} unless errors.present? + { sort => errors } end @@ -214,7 +227,7 @@ def self.bulk_corrections!(tutorial, assignment, files) submissions = Submission.where(tutorial: tutorial, assignment: assignment).proper report = { successful_saves: [], submissions: submissions.size, - invalid_filenames: [], invalid_id: [], in_subfolder: [], + invalid_filenames: [], invalid_id: [], in_subfolder: [], no_decision: [], rejected: [], invalid_file: [] } tmp_folder = Dir.mktmpdir begin @@ -225,7 +238,7 @@ def self.bulk_corrections!(tutorial, assignment, files) next end submission_id = File.basename(filename.split('-ID-').last, - File.extname(filename.split('-ID-').last)) + File.extname(filename.split('-ID-').last)) submission = Submission.find_by_id(submission_id) if !submission report[:invalid_id].push(filename) @@ -254,44 +267,45 @@ def self.bulk_corrections!(tutorial, assignment, files) private - def matching_lecture - return true if tutorial&.lecture == assignment&.lecture - errors.add(:tutorial, :lecture_not_matching) - end + def matching_lecture + return true if tutorial&.lecture == assignment&.lecture - def set_token - self.token = Submission.generate_token - end + errors.add(:tutorial, :lecture_not_matching) + end - def self.number_of_submissions(tutorial, assignment) - Submission.where(tutorial: tutorial, assignment: assignment) - .where.not(manuscript_data: nil).size - end + def set_token + self.token = Submission.generate_token + end - def self.number_of_corrections(tutorial, assignment) - Submission.where(tutorial: tutorial, assignment: assignment) - .where.not(correction_data: nil).size - end + def self.number_of_submissions(tutorial, assignment) + Submission.where(tutorial: tutorial, assignment: assignment) + .where.not(manuscript_data: nil).size + end - def self.number_of_late_submissions(tutorial, assignment) - Submission.where(tutorial: tutorial, assignment: assignment) - .where.not(manuscript_data: nil) - .select { |s| s.too_late? }.size - end + def self.number_of_corrections(tutorial, assignment) + Submission.where(tutorial: tutorial, assignment: assignment) + .where.not(correction_data: nil).size + end - def self.submissions_total(assignment) - Submission.where(assignment: assignment) - .where.not(manuscript_data: nil).size - end + def self.number_of_late_submissions(tutorial, assignment) + Submission.where(tutorial: tutorial, assignment: assignment) + .where.not(manuscript_data: nil) + .select { |s| s.too_late? }.size + end - def self.corrections_total(assignment) - Submission.where(assignment: assignment) - .where.not(correction_data: nil).size - end + def self.submissions_total(assignment) + Submission.where(assignment: assignment) + .where.not(manuscript_data: nil).size + end - def self.late_submissions_total(assignment) - Submission.where(assignment: assignment) - .where.not(manuscript_data: nil) - .select { |s| s.too_late? }.size - end + def self.corrections_total(assignment) + Submission.where(assignment: assignment) + .where.not(correction_data: nil).size + end + + def self.late_submissions_total(assignment) + Submission.where(assignment: assignment) + .where.not(manuscript_data: nil) + .select { |s| s.too_late? }.size + end end diff --git a/app/models/submission_cleaner.rb b/app/models/submission_cleaner.rb index 9f4e7681f..5a91644cc 100644 --- a/app/models/submission_cleaner.rb +++ b/app/models/submission_cleaner.rb @@ -50,75 +50,75 @@ def check_for_deletion private - def clear_props - @assignments = nil - @submitters = nil - @lectures = nil - end + def clear_props + @assignments = nil + @submitters = nil + @lectures = nil + end - def fetch_props - clear_props - @assignments = Assignment.where(deletion_date: @deletion_date) - return if @assignments.empty? + def fetch_props + clear_props + @assignments = Assignment.where(deletion_date: @deletion_date) + return if @assignments.empty? - @submitters = User.where(id: @assignments.flat_map(&:submitter_ids)) - @lectures = Lecture.where(id: @assignments.pluck(:lecture_id)) - end + @submitters = User.where(id: @assignments.flat_map(&:submitter_ids)) + @lectures = Lecture.where(id: @assignments.pluck(:lecture_id)) + end - def send_destruction_mail_to_submitters - return if @submitters.blank? + def send_destruction_mail_to_submitters + return if @submitters.blank? - I18n.available_locales.each do |l| - local_submitter_ids = @submitters.where(locale: l).pluck(:id) - next if local_submitter_ids.empty? + I18n.available_locales.each do |l| + local_submitter_ids = @submitters.where(locale: l).pluck(:id) + next if local_submitter_ids.empty? - local_submitter_ids.in_groups_of(200, false) do |group| - NotificationMailer.with(recipients: group, - deletion_date: @deletion_date, - locale: l) - .submission_destruction_email.deliver_now + local_submitter_ids.in_groups_of(200, false) do |group| + NotificationMailer.with(recipients: group, + deletion_date: @deletion_date, + locale: l) + .submission_destruction_email.deliver_now + end end end - end - def send_destruction_mail_to_editors - @lectures.each do |l| - editor_ids = l.editors.pluck(:id) + [l.teacher.id] - NotificationMailer.with(recipients: editor_ids, - lecture: l, - deletion_date: @deletion_date, - locale: l.locale) - .submission_destruction_lecture_email.deliver_now + def send_destruction_mail_to_editors + @lectures.each do |l| + editor_ids = l.editors.pluck(:id) + [l.teacher.id] + NotificationMailer.with(recipients: editor_ids, + lecture: l, + deletion_date: @deletion_date, + locale: l.locale) + .submission_destruction_lecture_email.deliver_now + end end - end - - def send_info_mail_to_submitters - return if @submitters.blank? - I18n.available_locales.each do |l| - local_submitter_ids = @submitters.where(locale: l).pluck(:id) - next if local_submitter_ids.empty? + def send_info_mail_to_submitters + return if @submitters.blank? + + I18n.available_locales.each do |l| + local_submitter_ids = @submitters.where(locale: l).pluck(:id) + next if local_submitter_ids.empty? + + local_submitter_ids.in_groups_of(200, false) do |group| + NotificationMailer.with(recipients: group, + deletion_date: @deletion_date, + reminder: @reminder, + lectures: @lectures, + locale: l) + .submission_deletion_email.deliver_now + end + end + end - local_submitter_ids.in_groups_of(200, false) do |group| - NotificationMailer.with(recipients: group, + def send_info_mail_to_editors + @lectures.each do |l| + editor_ids = l.editors.pluck(:id) + [l.teacher.id] + NotificationMailer.with(recipients: editor_ids, + lecture: l, deletion_date: @deletion_date, reminder: @reminder, - lectures: @lectures, - locale: l) - .submission_deletion_email.deliver_now + locale: l.locale) + .submission_deletion_lecture_email.deliver_now end end - end - - def send_info_mail_to_editors - @lectures.each do |l| - editor_ids = l.editors.pluck(:id) + [l.teacher.id] - NotificationMailer.with(recipients: editor_ids, - lecture: l, - deletion_date: @deletion_date, - reminder: @reminder, - locale: l.locale) - .submission_deletion_lecture_email.deliver_now - end - end end diff --git a/app/models/tag.rb b/app/models/tag.rb index dc37e6361..42d1499a0 100644 --- a/app/models/tag.rb +++ b/app/models/tag.rb @@ -38,15 +38,19 @@ class Tag < ApplicationRecord serialize :realizations, Array accepts_nested_attributes_for :notions, - reject_if: lambda {|attributes| attributes['title'].blank?}, - allow_destroy: true + reject_if: lambda { |attributes| + attributes['title'].blank? + }, + allow_destroy: true validates_presence_of :notions validates_associated :notions accepts_nested_attributes_for :aliases, - reject_if: lambda {|attributes| attributes['title'].blank?}, - allow_destroy: true + reject_if: lambda { |attributes| + attributes['title'].blank? + }, + allow_destroy: true validates_associated :aliases @@ -84,6 +88,7 @@ def extended_title_uncached unless other_titles_uncached.any? return local_title_uncached + " (#{aliases.pluck(:title).join(', ')})" end + local_title_uncached + " (#{aliases.pluck(:title).join(', ')}," + " #{other_titles_uncached.join(', ')})" @@ -144,7 +149,6 @@ def self.select_with_substring(search_string) .map { |t| { value: t.id, text: t.title } } end - # returns all tags whose title is close to the given search string # wrt to the JaroWinkler metric def self.similar_tags(search_string) @@ -174,7 +178,7 @@ def self.select_by_title def self.select_by_title_except(excluded_tags) Tag.where.not(id: excluded_tags.pluck(:id)) .map { |t| t.extended_title_id_hash } - .natural_sort_by{ |t| t[:title] }.map { |t| [t[:title], t[:id]] } + .natural_sort_by { |t| t[:title] }.map { |t| [t[:title], t[:id]] } end # converts the subgraph of all tags of distance <= 2 to the given marked tag @@ -208,6 +212,7 @@ def realizations_cached # search params is a hash having keys :all_tags, :tag_ids def self.search_tags(search_params) return Tag.all unless search_params[:all_tags] == '0' + tag_ids = search_params[:tag_ids] || [] Tag.where(id: tag_ids) end @@ -238,6 +243,7 @@ def tags_in_neighbourhood def short_title(max_letters = 30) return title unless title.length > max_letters + title[0, max_letters - 3] + '...' end @@ -265,6 +271,7 @@ def lectures def create_random_quiz!(user) questions = visible_questions(user) return unless questions.any? + question_ids = questions.pluck(:id).sample(5) quiz_graph = QuizGraph.build_from_questions(question_ids) quiz = Quiz.new(description: "#{I18n.t('categories.randomquiz.singular')} #{title} #{Time.now}", @@ -273,6 +280,7 @@ def create_random_quiz!(user) sort: 'RandomQuiz') quiz.save return quiz.errors unless quiz.valid? + quiz end @@ -281,6 +289,7 @@ def create_random_quiz!(user) def color(marked_tag, highlight_related_tags: true) return '#f00' if self == marked_tag return '#ff8c00' if highlight_related_tags && in?(marked_tag.related_tags) + '#000' end @@ -289,6 +298,7 @@ def color(marked_tag, highlight_related_tags: true) def background(marked_tag, highlight_related_tags: true) return '#f00' if self == marked_tag return '#ff8c00' if highlight_related_tags && in?(marked_tag.related_tags) + '#666' end @@ -313,7 +323,9 @@ def cytoscape_edge(related_tag) # published sections are sections that belong to a published lecture def visible_sections(user) - user.filter_sections(sections).select { |s| s.lecture.visible_for_user?(user) } + user.filter_sections(sections).select { |s| + s.lecture.visible_for_user?(user) + } end def cache_key @@ -343,6 +355,7 @@ def identify_with!(tag) related_tags.delete(tag) tag.sections.each do |s| next unless self.in?(s.tags) + old_section_tag = SectionTagJoin.find_by(section: s, tag: tag) position = old_section_tag.tag_position new_section_tag = SectionTagJoin.find_by(section: s, tag: self) @@ -369,24 +382,25 @@ def visible_questions(user) private - def touch_relations(notion) - if persisted? - touch - touch_lectures - touch_sections - touch_chapters + def touch_relations(notion) + if persisted? + touch + touch_lectures + touch_sections + touch_chapters + end end - end - # simulates the after_destroy callback for relations - def destroy_relations(related_tag) - Relation.where(tag: [self, related_tag], - related_tag: [self, related_tag]).delete_all - end + # simulates the after_destroy callback for relations + def destroy_relations(related_tag) + Relation.where(tag: [self, related_tag], + related_tag: [self, related_tag]).delete_all + end - def title_join - result = notions.pluck(:title).join(' ') - return result unless aliases.any? - result + ' ' + aliases.pluck(:title).join(' ') - end + def title_join + result = notions.pluck(:title).join(' ') + return result unless aliases.any? + + result + ' ' + aliases.pluck(:title).join(' ') + end end diff --git a/app/models/talk.rb b/app/models/talk.rb index e34fbf05e..5b528e139 100644 --- a/app/models/talk.rb +++ b/app/models/talk.rb @@ -8,7 +8,7 @@ class Talk < ApplicationRecord # being a teachable (course/lecture/lesson), a talk has associated media has_many :media, -> { order(position: :asc) }, as: :teachable, - dependent: :destroy + dependent: :destroy # a talk has many tags has_many :talk_tag_joins, dependent: :destroy diff --git a/app/models/term.rb b/app/models/term.rb index 43279cd49..36009cad4 100644 --- a/app/models/term.rb +++ b/app/models/term.rb @@ -41,6 +41,7 @@ def end_date # label contains season and year(s) with all digits def to_label return unless season.present? + season + ' ' + year_corrected end @@ -101,12 +102,15 @@ def self.possible_deletion_dates end def self.possible_deletion_dates_localized - possible_deletion_dates.map { |d| d.strftime(I18n.t('date.formats.concise')) } + possible_deletion_dates.map { |d| + d.strftime(I18n.t('date.formats.concise')) + } end # array of all terms together with their ids for use in options_for_select def self.select_terms(independent = false) return ['bla', nil] if independent + Term.all.sort_by(&:begin_date).reverse.map { |t| [t.to_label, t.id] } end @@ -120,24 +124,26 @@ def self.previous_by_date(date) private - def year_corrected - return year.to_s unless season == 'WS' - year.to_s + '/' + ((year % 100) + 1).to_s - end + def year_corrected + return year.to_s unless season == 'WS' - def year_corrected_short - return (year % 100).to_s unless season == 'WS' - (year % 100).to_s + '/' + ((year % 100) + 1).to_s - end + year.to_s + '/' + ((year % 100) + 1).to_s + end - def touch_lectures_and_lessons - lectures.update_all(updated_at: Time.now) - Lesson.where(lecture: lectures).update_all(updated_at: Time.now) - end + def year_corrected_short + return (year % 100).to_s unless season == 'WS' - def touch_media - Medium.where(teachable: lectures).update_all(updated_at: Time.now) - Medium.where(teachable: Lesson.where(lecture: lectures)) - .update_all(updated_at: Time.now) - end + (year % 100).to_s + '/' + ((year % 100) + 1).to_s + end + + def touch_lectures_and_lessons + lectures.update_all(updated_at: Time.now) + Lesson.where(lecture: lectures).update_all(updated_at: Time.now) + end + + def touch_media + Medium.where(teachable: lectures).update_all(updated_at: Time.now) + Medium.where(teachable: Lesson.where(lecture: lectures)) + .update_all(updated_at: Time.now) + end end diff --git a/app/models/time_stamp.rb b/app/models/time_stamp.rb index d5232735c..1924e36bb 100644 --- a/app/models/time_stamp.rb +++ b/app/models/time_stamp.rb @@ -81,27 +81,28 @@ def total_seconds private - def init_with_total_seconds(total_s) - floor_s = total_s.floor - @milliseconds = ((total_s - floor_s) * 1000).round - @minutes = (floor_s / 60) % 60 - @seconds = floor_s % 60 - @hours = floor_s / (60 * 60) - end + def init_with_total_seconds(total_s) + floor_s = total_s.floor + @milliseconds = ((total_s - floor_s) * 1000).round + @minutes = (floor_s / 60) % 60 + @seconds = floor_s % 60 + @hours = floor_s / (60 * 60) + end - def init_with_time_string(time_string) - return unless /(\d+):([0-5]\d):([0-5]\d).(\d{3})/.match?(time_string) - matchdata = /(\d):([0-5]\d):([0-5]\d).(\d{3})/.match(time_string) - @hours = matchdata[1].to_i - @minutes = matchdata[2].to_i - @seconds = matchdata[3].to_i - @milliseconds = matchdata[4].to_i - end + def init_with_time_string(time_string) + return unless /(\d+):([0-5]\d):([0-5]\d).(\d{3})/.match?(time_string) - def init_with_hms(params) - @hours = params[:h].to_i - @minutes = params[:m].to_i - @seconds = params[:s].to_i - @milliseconds = params[:ms].to_i - end + matchdata = /(\d):([0-5]\d):([0-5]\d).(\d{3})/.match(time_string) + @hours = matchdata[1].to_i + @minutes = matchdata[2].to_i + @seconds = matchdata[3].to_i + @milliseconds = matchdata[4].to_i + end + + def init_with_hms(params) + @hours = params[:h].to_i + @minutes = params[:m].to_i + @seconds = params[:s].to_i + @milliseconds = params[:ms].to_i + end end diff --git a/app/models/tutorial.rb b/app/models/tutorial.rb index b093de301..60611c8a7 100644 --- a/app/models/tutorial.rb +++ b/app/models/tutorial.rb @@ -14,20 +14,21 @@ class Tutorial < ApplicationRecord validates :title, uniqueness: { scope: [:lecture_id] }, presence: true def title_with_tutors - return "#{title}, #{I18n.t('basics.tba')}" unless tutors.any? - "#{title}, #{tutor_names}" + return "#{title}, #{I18n.t('basics.tba')}" unless tutors.any? + + "#{title}, #{tutor_names}" end def tutor_names - return unless tutors.any? - tutors.map(&:tutorial_name).join(', ') + return unless tutors.any? + + tutors.map(&:tutorial_name).join(', ') end def destructible? - Submission.where(tutorial: self).proper.none? + Submission.where(tutorial: self).proper.none? end - def teams_to_csv(assignment) submissions = Submission.where(tutorial: self, assignment: assignment) .proper.order(:last_modification_by_users_at) @@ -37,11 +38,11 @@ def teams_to_csv(assignment) end end end - + private - def check_destructibility - throw(:abort) unless destructible? - true - end + def check_destructibility + throw(:abort) unless destructible? + true + end end diff --git a/app/models/user.rb b/app/models/user.rb index 58a29840f..0c4dec0a8 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -66,7 +66,8 @@ class User < ApplicationRecord # if a homepage is given it should at leat be a valid address validates :homepage, http_url: true, if: :homepage? - validates :locale, inclusion: { in: I18n.available_locales.map(&:to_s) }, if: :locale? + validates :locale, inclusion: { in: I18n.available_locales.map(&:to_s) }, + if: :locale? # a user needs to give a display name validates :name, presence: true, if: :persisted? @@ -101,7 +102,6 @@ class User < ApplicationRecord text :tutorial_name end - # returns the array of all teachers def self.teachers User.where(id: Lecture.pluck(:teacher_id).uniq) @@ -137,6 +137,7 @@ def self.only_editors_selection # search params is a hash having keys :all_editors, :editor_ids def self.search_editors(search_params) return User.editors unless search_params[:all_editors] == '0' + editor_ids = search_params[:editor_ids] || [] User.where(id: editor_ids) end @@ -145,7 +146,7 @@ def self.search_editors(search_params) # (e.g. in a select editors form) def self.select_editors User.pluck(:name, :email, :id, :name_in_tutorials) - .map { |u| [ "#{u.fourth.presence || u.first} (#{u.second})", u.third] } + .map { |u| ["#{u.fourth.presence || u.first} (#{u.second})", u.third] } end def self.name_or_email_like(search_string) @@ -165,14 +166,17 @@ def self.preferred_name_or_email_like(search_string) return User.none unless search_string.length >= 2 where(name_in_tutorials: [nil, '']).name_or_email_like(search_string) - .or(where.not(name_in_tutorials: [nil, '']) + .or(where.not(name_in_tutorials: [nil, + '']) .name_in_tutorials_or_email_like(search_string)) end def self.values_for_select pluck(:id, :name, :name_in_tutorials, :email) - .map { |u| { value: u.first, - text: "#{ u.third.presence || u.second } (#{u.fourth})" } } + .map { |u| + { value: u.first, + text: "#{u.third.presence || u.second} (#{u.fourth})" } + } end def courses @@ -186,11 +190,13 @@ def courses # - all courses that the user has subscribed to (if subscription type is 3) def related_courses(overrule_subscription_type: false) return if subscription_type.nil? + selection_type = overrule_subscription_type || subscription_type if selection_type == 1 return Course.where(id: preceding_course_ids).includes(:lectures) end return Course.all.includes(:lectures) if selection_type == 2 + courses end @@ -214,8 +220,10 @@ def related_lectures # returns ARel of all those tags from the given tags that belong to # the user's related lectures def filter_tags(tags) - Tag.where(id: tags.select { |t| t.in_lectures?(related_lectures) || - t.in_courses?(related_courses) } + Tag.where(id: tags.select { |t| + t.in_lectures?(related_lectures) || + t.in_courses?(related_courses) + } .map(&:id)) end @@ -229,9 +237,9 @@ def filter_lectures(lectures) # the user's related lectures def filter_media(media) media.where(teachable: related_lectures) - .or(media.where(teachable: related_courses)) - .or(media.where(teachable: Lesson.where(lecture: related_lectures))) - .or(media.where(teachable: Talk.where(lecture: related_lectures))) + .or(media.where(teachable: related_courses)) + .or(media.where(teachable: Lesson.where(lecture: related_lectures))) + .or(media.where(teachable: Talk.where(lecture: related_lectures))) end # returns array of all those sections from the given sections that belon to @@ -305,6 +313,7 @@ def editor? def info_uncached return email unless name.present? + (name_in_tutorials.presence || name) + ' (' + email + ')' end @@ -316,6 +325,7 @@ def info def tutorial_info_uncached return email unless tutorial_name.present? + tutorial_name + ' (' + email + ')' end @@ -327,6 +337,7 @@ def tutorial_info def name_or_email return name unless name.blank? + email end @@ -336,6 +347,7 @@ def tutorial_name def short_info return email unless name.present? + name end @@ -405,9 +417,10 @@ def unrelated_courses # boards not belonging to lectures def thredded_can_read_messageboards return Thredded::Messageboard.all if admin? + subscribed_forums = Thredded::Messageboard.where(id: lectures.map(&:forum_id)) - .or(Thredded::Messageboard.where.not(id: Lecture.all.map(&:forum_id))) + .or(Thredded::Messageboard.where.not(id: Lecture.all.map(&:forum_id))) if teacher? || edited_courses.any? || edited_lectures.any? return Thredded::Messageboard.where(id: teaching_related_lectures .map(&:forum_id)) @@ -421,6 +434,7 @@ def thredded_can_read_messageboards # lecture (they are for admins posts only) def thredded_can_write_messageboards return Thredded::Messageboard.all if admin? + subscribed_forums = Thredded::Messageboard.where(id: lectures.map(&:forum_id)) if teacher? || edited_courses.any? || edited_lectures.any? @@ -438,6 +452,7 @@ def thredded_can_write_messageboards # - none otherwise def thredded_can_moderate_messageboards return Thredded::Messageboard.all if admin? + if teacher? || edited_courses.any? || edited_lectures.any? return Thredded::Messageboard.where(id: teaching_related_lectures .map(&:forum_id)) @@ -464,25 +479,26 @@ def filter_visible_media(media) nonsubscribed_talks = Talk.where(lecture: nonsubscribed_lectures) edited_talks = Talk.where(lecture: teaching_related_lectures) return media if admin + media.where(teachable: courses, released: ['all', 'subscribers', 'users']) - .or(media.where(teachable: nonsubscribed_courses, - released: ['all', 'users'])) - .or(media.where(teachable: lectures, - released: ['all', 'subscribers', 'users'])) - .or(media.where(teachable: nonsubscribed_lectures, - released: ['all', 'users'])) - .or(media.where(teachable: lessons, - released: ['all', 'subscribers', 'users'])) - .or(media.where(teachable: nonsubscribed_lessons, - released: ['all', 'users'])) - .or(media.where(teachable: talks, - released: ['all', 'subscribers', 'users'])) - .or(media.where(teachable: nonsubscribed_talks, - released: ['all', 'users'])) - .or(media.where(teachable: edited_courses)) - .or(media.where(teachable: teaching_related_lectures)) - .or(media.where(teachable: edited_lessons)) - .or(media.where(teachable: edited_talks)) + .or(media.where(teachable: nonsubscribed_courses, + released: ['all', 'users'])) + .or(media.where(teachable: lectures, + released: ['all', 'subscribers', 'users'])) + .or(media.where(teachable: nonsubscribed_lectures, + released: ['all', 'users'])) + .or(media.where(teachable: lessons, + released: ['all', 'subscribers', 'users'])) + .or(media.where(teachable: nonsubscribed_lessons, + released: ['all', 'users'])) + .or(media.where(teachable: talks, + released: ['all', 'subscribers', 'users'])) + .or(media.where(teachable: nonsubscribed_talks, + released: ['all', 'users'])) + .or(media.where(teachable: edited_courses)) + .or(media.where(teachable: teaching_related_lectures)) + .or(media.where(teachable: edited_lessons)) + .or(media.where(teachable: edited_talks)) end def subscribed_commentable_media_with_comments @@ -496,11 +512,13 @@ def subscribed_commentable_media_with_comments def media_latest_comments subscribed_commentable_media_with_comments - .map { |m| { medium: m, - thread: m.commontator_thread, - latest_comment: m.commontator_thread - .comments.sort_by(&:created_at) - .last } } + .map { |m| + { medium: m, + thread: m.commontator_thread, + latest_comment: m.commontator_thread + .comments.sort_by(&:created_at) + .last } + } .sort_by { |x| x[:latest_comment].created_at }.reverse end @@ -524,8 +542,9 @@ def anonymized_id def subscribe_lecture!(lecture) return false unless lecture.is_a?(Lecture) return false if lecture.in?(lectures) + lectures << lecture - + # make sure subscribed_users is updated in media Sunspot.index! lecture.media @@ -535,6 +554,7 @@ def subscribe_lecture!(lecture) def unsubscribe_lecture!(lecture) return false unless lecture.is_a?(Lecture) return false unless lecture.in?(lectures) + lectures.delete(lecture) favorite_lectures.delete(lecture) @@ -555,6 +575,7 @@ def current_subscribable_lectures unless editor? || teacher? return current_lectures.published.sort + no_term_lectures.published.sort end + current_lectures.select { |l| l.edited_by?(self) || l.published? }.sort + no_term_lectures.select { |l| l.edited_by?(self) || l.published? }.sort end @@ -667,6 +688,7 @@ def can_edit?(something) raise 'can_edit? was called with incompatible class' end return true if admin + in?(something.editors_with_inheritance.to_a) end @@ -676,6 +698,7 @@ def speaker? def layout return 'administration' if admin_or_editor? + 'application_no_sidebar' end @@ -693,48 +716,49 @@ def generic? private - def set_defaults - self.subscription_type ||= 1 - self.admin ||= false - self.name ||= email.split('@').first - self.locale ||= I18n.default_locale.to_s - end + def set_defaults + self.subscription_type ||= 1 + self.admin ||= false + self.name ||= email.split('@').first + self.locale ||= I18n.default_locale.to_s + end - # sets time for DSGVO consent to current time - def set_consented_at - update(consented_at: Time.now) - end + # sets time for DSGVO consent to current time + def set_consented_at + update(consented_at: Time.now) + end - # returns array of ids of all courses that preced the subscribed courses - def preceding_course_ids - courses.all.map { |l| l.preceding_courses.pluck(:id) }.flatten + - courses.all.pluck(:id) - end + # returns array of ids of all courses that preced the subscribed courses + def preceding_course_ids + courses.all.map { |l| l.preceding_courses.pluck(:id) }.flatten + + courses.all.pluck(:id) + end - def destroy_single_submissions - Submission.where(id: submissions.select { |s| s.users.count == 1 } - .map(&:id)).destroy_all - end + def destroy_single_submissions + Submission.where(id: submissions.select { |s| s.users.count == 1 } + .map(&:id)).destroy_all + end - def archive_email - splitting = DefaultSetting::PROJECT_EMAIL.split('@') - "#{splitting.first}-archive-#{id}@#{splitting.second}" - end + def archive_email + splitting = DefaultSetting::PROJECT_EMAIL.split('@') + "#{splitting.first}-archive-#{id}@#{splitting.second}" + end - def transfer_contributions_to(user) - return false unless user && user.valid? && user != self - given_lectures.update_all(teacher_id: user.id) - EditableUserJoin.where(user: self, editable_type: 'Medium') - .update_all(user_id: user.id) - end + def transfer_contributions_to(user) + return false unless user && user.valid? && user != self - def archive_user(archive_name) - User.create(name: archive_name, - email: archive_email, - password: SecureRandom.base58(12), - consents: true, - consented_at: Time.now, - confirmed_at: Time.now, - archived: true) - end + given_lectures.update_all(teacher_id: user.id) + EditableUserJoin.where(user: self, editable_type: 'Medium') + .update_all(user_id: user.id) + end + + def archive_user(archive_name) + User.create(name: archive_name, + email: archive_email, + password: SecureRandom.base58(12), + consents: true, + consented_at: Time.now, + confirmed_at: Time.now, + archived: true) + end end diff --git a/app/models/user_cleaner.rb b/app/models/user_cleaner.rb index 02a58466b..9c40eecba 100644 --- a/app/models/user_cleaner.rb +++ b/app/models/user_cleaner.rb @@ -4,7 +4,8 @@ class UserCleaner def login @imap = Net::IMAP.new(ENV['IMAPSERVER'], port: 993, ssl: true) - @imap.authenticate('LOGIN', ENV['PROJECT_EMAIL_USERNAME'], ENV['PROJECT_EMAIL_PASSWORD']) + @imap.authenticate('LOGIN', ENV['PROJECT_EMAIL_USERNAME'], + ENV['PROJECT_EMAIL_PASSWORD']) end def logout @@ -16,8 +17,10 @@ def search_emails_and_hashes @hash_dict = {} @imap.examine(ENV['PROJECT_EMAIL_MAILBOX']) # Mails containing multiple email addresses (Subject: "Undelivered Mail Returned to Sender") - @imap.search(['SUBJECT', 'Undelivered Mail Returned to Sender']).each do |message_id| - body = @imap.fetch(message_id, "BODY[TEXT]")[0].attr["BODY[TEXT]"].squeeze(" ") + @imap.search(['SUBJECT', + 'Undelivered Mail Returned to Sender']).each do |message_id| + body = @imap.fetch(message_id, + "BODY[TEXT]")[0].attr["BODY[TEXT]"].squeeze(" ") if match = body.scan(/([a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,4})[\s\S]*?([a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,4})[\s\S]*?User has moved to ERROR: Account expired/) match = match.flatten.uniq match.each do |email| @@ -34,8 +37,10 @@ def search_emails_and_hashes '([a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,4})>[\s\S]*?User unknown in virtual mailbox table' ] - @imap.search(['SUBJECT', 'Delivery Status Notification (Failure)']).each do |message_id| - body = @imap.fetch(message_id, "BODY[TEXT]")[0].attr["BODY[TEXT]"].squeeze(" ") + @imap.search(['SUBJECT', + 'Delivery Status Notification (Failure)']).each do |message_id| + body = @imap.fetch(message_id, + "BODY[TEXT]")[0].attr["BODY[TEXT]"].squeeze(" ") patterns.each do |pattern| if match = body.scan(/#{pattern}/) match = match.flatten.uniq @@ -87,10 +92,11 @@ def delete_ghosts end end - def move_mail(message_ids, attempt=0) + def move_mail(message_ids, attempt = 0) return if message_ids.blank? + message_ids = Array(message_ids) - if attempt>3 + if attempt > 3 return end @@ -98,7 +104,7 @@ def move_mail(message_ids, attempt=0) @imap.examine(ENV['PROJECT_EMAIL_MAILBOX']) @imap.move(message_ids, "Other Users/mampf/handled_bounces") rescue Net::IMAP::BadResponseError - move_mail(message_ids, attempt=attempt+1) + move_mail(message_ids, attempt = attempt + 1) end end @@ -106,11 +112,11 @@ def clean! login search_emails_and_hashes return if @email_dict.blank? + send_hashes sleep(10) search_emails_and_hashes delete_ghosts logout end - -end \ No newline at end of file +end diff --git a/app/models/user_submission_join.rb b/app/models/user_submission_join.rb index d0c6b6d01..9218519c6 100644 --- a/app/models/user_submission_join.rb +++ b/app/models/user_submission_join.rb @@ -6,23 +6,26 @@ class UserSubmissionJoin < ApplicationRecord validate :max_team_size, on: :create def assignment - submission.assignment + submission.assignment end private - def only_one_per_assignment - if UserSubmissionJoin.where(user: user, submission: assignment.submissions) - .none? - return true - end - errors.add(:base, :only_one_per_assignment) - end + def only_one_per_assignment + if UserSubmissionJoin.where(user: user, + submission: assignment.submissions) + .none? + return true + end - def max_team_size - lecture = submission.assignment.lecture - return true unless lecture.submission_max_team_size - return true if submission.users.size < lecture.submission_max_team_size - errors.add(:base, :team_size) - end + errors.add(:base, :only_one_per_assignment) + end + + def max_team_size + lecture = submission.assignment.lecture + return true unless lecture.submission_max_team_size + return true if submission.users.size < lecture.submission_max_team_size + + errors.add(:base, :team_size) + end end diff --git a/app/uploaders/correction_uploader.rb b/app/uploaders/correction_uploader.rb index a17390266..cc7a30ba3 100644 --- a/app/uploaders/correction_uploader.rb +++ b/app/uploaders/correction_uploader.rb @@ -4,6 +4,6 @@ class CorrectionUploader < Shrine # shrine plugins plugin :determine_mime_type, analyzer: :marcel - plugin :upload_endpoint, max_size: 30*1024*1024 # 30 MB + plugin :upload_endpoint, max_size: 30 * 1024 * 1024 # 30 MB plugin :default_storage, cache: :submission_cache, store: :submission_store end diff --git a/app/uploaders/geogebra_uploader.rb b/app/uploaders/geogebra_uploader.rb index 5e11e9977..32fe00ff5 100644 --- a/app/uploaders/geogebra_uploader.rb +++ b/app/uploaders/geogebra_uploader.rb @@ -24,4 +24,4 @@ class GeogebraUploader < Shrine end { screenshot: File.open(unzipped) } end -end \ No newline at end of file +end diff --git a/app/uploaders/pdf_uploader.rb b/app/uploaders/pdf_uploader.rb index d75b6add2..bacdb9bcc 100644 --- a/app/uploaders/pdf_uploader.rb +++ b/app/uploaders/pdf_uploader.rb @@ -30,14 +30,14 @@ class PdfUploader < Shrine # extract lines that correspond to MaMpf-Label entries from LaTEX # package mampf.sty structure = if File.file?(structure_path) - open(structure_path, "r") do - |io| io.read.encode("UTF-8", invalid: :replace) - end - end + open(structure_path, "r") do |io| + io.read.encode("UTF-8", invalid: :replace) + end + end structure ||= '' bookmarks = structure.scan(/MaMpf-Label\|(.*?)\n/).flatten result = [] - bookmarks.each_with_index do |b,i| + bookmarks.each_with_index do |b, i| # extract bookmark data # line may look like this: # defn:erster-Tag|Definition|1.1|Erster Tag|1 diff --git a/app/uploaders/submission_uploader.rb b/app/uploaders/submission_uploader.rb index 243402594..dd8d4f9e5 100644 --- a/app/uploaders/submission_uploader.rb +++ b/app/uploaders/submission_uploader.rb @@ -4,7 +4,7 @@ class SubmissionUploader < Shrine # shrine plugins plugin :determine_mime_type, analyzer: :marcel - plugin :upload_endpoint, max_size: 20*1024*1024 # 20 MB - plugin :default_storage, cache: :submission_cache, store: :submission_store - plugin :restore_cached_data + plugin :upload_endpoint, max_size: 20 * 1024 * 1024 # 20 MB + plugin :default_storage, cache: :submission_cache, store: :submission_store + plugin :restore_cached_data end diff --git a/app/uploaders/video_uploader.rb b/app/uploaders/video_uploader.rb index 3613a50fb..4e42b3134 100644 --- a/app/uploaders/video_uploader.rb +++ b/app/uploaders/video_uploader.rb @@ -16,8 +16,8 @@ class VideoUploader < Shrine if options[:action] != :upload movie = Shrine.with_file(io) { |file| FFMPEG::Movie.new(file.path) } - { 'duration' => movie.duration, - 'bitrate' => movie.bitrate, + { 'duration' => movie.duration, + 'bitrate' => movie.bitrate, 'resolution' => movie.resolution, 'frame_rate' => movie.frame_rate } end diff --git a/app/uploaders/vtt_uploader.rb b/app/uploaders/vtt_uploader.rb index d6cc77163..0e8dfdcce 100644 --- a/app/uploaders/vtt_uploader.rb +++ b/app/uploaders/vtt_uploader.rb @@ -2,4 +2,4 @@ class VttUploader < Shrine plugin :pretty_location plugin :determine_mime_type -end \ No newline at end of file +end diff --git a/app/uploaders/zip_uploader.rb b/app/uploaders/zip_uploader.rb index f2ce1220d..353280167 100644 --- a/app/uploaders/zip_uploader.rb +++ b/app/uploaders/zip_uploader.rb @@ -3,15 +3,15 @@ class ZipUploader < Shrine # shrine plugins plugin :determine_mime_type, analyzer: :marcel plugin :validation_helpers - plugin :upload_endpoint, max_size: 1024*1024*1024 # 1 GB - plugin :default_storage, cache: :submission_cache, store: :submission_store + plugin :upload_endpoint, max_size: 1024 * 1024 * 1024 # 1 GB + plugin :default_storage, cache: :submission_cache, store: :submission_store Attacher.validate do validate_mime_type_inclusion %w[application/zip], - message: - I18n.t('package.no_zip') - # maximum size of 1 GB - validate_max_size 1024*1024*1024, - message: I18n.t('package.too_big') + message: + I18n.t('package.no_zip') + # maximum size of 1 GB + validate_max_size 1024 * 1024 * 1024, + message: I18n.t('package.too_big') end -end \ No newline at end of file +end diff --git a/app/validators/http_url_validator.rb b/app/validators/http_url_validator.rb index 68806ee14..7dcf6ac83 100644 --- a/app/validators/http_url_validator.rb +++ b/app/validators/http_url_validator.rb @@ -1,5 +1,4 @@ class HttpUrlValidator < ActiveModel::EachValidator - def self.compliant?(value) uri = URI.parse(Addressable::URI.encode(value)) uri.is_a?(URI::HTTP) && !uri.host.nil? diff --git a/app/views/users/list.json.jbuilder b/app/views/users/list.json.jbuilder index 87cfb45e1..c6df8a8ba 100644 --- a/app/views/users/list.json.jbuilder +++ b/app/views/users/list.json.jbuilder @@ -1,4 +1,4 @@ json.array! @users do |user| json.id user.id json.text user.tutorial_info -end \ No newline at end of file +end diff --git a/app/workers/cache_cleaner.rb b/app/workers/cache_cleaner.rb index 566ddfd47..6b77f4bf4 100644 --- a/app/workers/cache_cleaner.rb +++ b/app/workers/cache_cleaner.rb @@ -7,4 +7,4 @@ def perform submission_cache.clear! { |path| path.mtime < Time.now - 1.week } media_cache.clear! { |path| path.mtime < Time.now - 1.week } end -end \ No newline at end of file +end diff --git a/app/workers/consumption_saver.rb b/app/workers/consumption_saver.rb index ca1d9add6..924772b77 100644 --- a/app/workers/consumption_saver.rb +++ b/app/workers/consumption_saver.rb @@ -6,4 +6,4 @@ def perform(medium_id, mode, sort) mode: mode, sort: sort) end -end \ No newline at end of file +end diff --git a/app/workers/interaction_saver.rb b/app/workers/interaction_saver.rb index 05fa6b36f..ec89207ad 100644 --- a/app/workers/interaction_saver.rb +++ b/app/workers/interaction_saver.rb @@ -3,12 +3,12 @@ class InteractionSaver def perform(session_id, full_path, referrer, study_participant) referrer_url = if referrer.to_s.include?(ENV['URL_HOST']) - referrer.to_s.remove(ENV['URL_HOST']) - .remove('https://').remove('http://') - end + referrer.to_s.remove(ENV['URL_HOST']) + .remove('https://').remove('http://') + end Interaction.create(session_id: Digest::SHA2.hexdigest(session_id).first(10), full_path: full_path, referrer_url: referrer_url, study_participant: study_participant) end -end \ No newline at end of file +end diff --git a/app/workers/media_publisher.rb b/app/workers/media_publisher.rb index e847da8b3..17318e427 100644 --- a/app/workers/media_publisher.rb +++ b/app/workers/media_publisher.rb @@ -7,4 +7,4 @@ def perform .map(&:medium_id) Medium.where(id: media_ids).each(&:publish!) end -end \ No newline at end of file +end diff --git a/app/workers/metadata_extractor.rb b/app/workers/metadata_extractor.rb index 428ecfb9f..08384735b 100644 --- a/app/workers/metadata_extractor.rb +++ b/app/workers/metadata_extractor.rb @@ -4,8 +4,9 @@ class MetadataExtractor def perform(medium_id) medium = Medium.find(medium_id) return unless medium && medium.video.present? + medium.video.refresh_metadata!(action: :store) refreshed_video = medium.video medium.update(video_data: refreshed_video.to_json) end -end \ No newline at end of file +end diff --git a/app/workers/probe_saver.rb b/app/workers/probe_saver.rb index 83a86c09c..553f5399e 100644 --- a/app/workers/probe_saver.rb +++ b/app/workers/probe_saver.rb @@ -12,7 +12,8 @@ def perform(quiz_id, question_id, remark_id, correct, progress, session_id, study_participant: study_participant, input: input) return unless progress == -1 + success = Probe.where(session_id: session_id, correct: true).count probe.update(success: success) end -end \ No newline at end of file +end diff --git a/app/workers/submissions_cleaner.rb b/app/workers/submissions_cleaner.rb index e5660eb1f..14bebf6ed 100644 --- a/app/workers/submissions_cleaner.rb +++ b/app/workers/submissions_cleaner.rb @@ -5,4 +5,4 @@ def perform submission_cleaner = SubmissionCleaner.new(date: Time.zone.today) submission_cleaner.clean! end -end \ No newline at end of file +end diff --git a/app/workers/user_cleaner_job.rb b/app/workers/user_cleaner_job.rb index d64a651bf..4c0187a89 100644 --- a/app/workers/user_cleaner_job.rb +++ b/app/workers/user_cleaner_job.rb @@ -5,4 +5,4 @@ def perform user_cleaner = UserCleaner.new() user_cleaner.clean! end -end \ No newline at end of file +end