# lib/tasks/upgrade.rake
#
# mainly checks that the system settings we need for the new code
# are in the db, if not adds them to db
#
# Walter McGinnis, 2008-01-15
#
namespace :kete do
  desc 'Do everything that we need done, like adding data to the db, for an upgrade.'
  task upgrade: [
    'kete:upgrade:add_new_baskets',
    'kete:upgrade:add_tech_admin',
    'kete:upgrade:add_new_system_settings',
    'kete:upgrade:add_new_default_topics',
    'kete:upgrade:change_zebra_password',
    'kete:upgrade:check_required_software',
    'kete:upgrade:add_missing_mime_types',
    'kete:upgrade:correct_basket_defaults',
    'kete:upgrade:expire_depreciated_rss_cache',
    'kete:upgrade:set_default_join_and_memberlist_policies',
    'kete:upgrade:make_baskets_approved_if_status_null',
    'kete:upgrade:ignore_default_baskets_if_setting_not_set',
    'zebra:load_initial_records',
    'kete:upgrade:update_existing_comments_commentable_private',
    'kete:tools:remove_robots_txt',
    'kete:upgrade:set_default_locale_for_existing_users',
    'kete:upgrade:ensure_logins_all_valid',
    'kete:upgrade:move_user_name_to_display_and_resolved_name',
    'kete:upgrade:add_basket_id_to_taggings',
    'kete:upgrade:make_baskets_private_notification_do_not_email',
    'kete:upgrade:add_nested_values_to_comments',
    'kete:upgrade:change_inset_to_position',
    'kete:upgrade:set_null_private_only_mappings_to_false',
    'kete:upgrade:set_default_import_archive_set_policy',
    'kete:upgrade:add_missing_users']
  namespace :upgrade do
    desc 'Privacy Controls require that Comment#commentable_private be set.  Update existing comments to have this data.'
    task update_existing_comments_commentable_private: :environment do
      comment_count = 0
      Comment.find(:all, conditions: 'commentable_private is null').each do |comment|
        comment.commentable_private = false if comment.commentable_private.blank?
        comment.save!
        comment_count += 1
      end
      p 'updated ' + comment_count.to_s + " existing comments that didn't have privacy set."
    end

    desc 'Add the new system settings that are missing from our system.'
    task add_new_system_settings: :environment do
      system_settings_from_yml = YAML.load_file("#{RAILS_ROOT}/db/bootstrap/system_settings.yml")

      printed_related_items_notice = false

      # for each system_setting from yml
      # check if it's in the db
      # if not, add it
      # system settings have unique names
      system_settings_from_yml.each do |setting_array|
        setting_hash = setting_array[1]

        # if there are existing system settings
        # drop id from hash, as we want to determine it dynamically
        # else we want to use the bootstap versions
        setting_hash.delete('id') if SystemSetting.count > 0

        if !SystemSetting.find_by_name(setting_hash['name'])

          if setting_hash['name'].include?('Related Items Position')
            # when we upgrade and add these new settings, we want to make sure
            # we mimic behaviour of the site beforehand, so related content
            # should be below and the option to change should be hidden
            case setting_hash['name']
            when 'Related Items Position Default'
              setting_hash['value'] = 'below'
            when 'Hide Related Items Position Field'
              setting_hash['value'] = 'true'
            end

            unless printed_related_items_notice
              puts ''
              puts '- Related Items Position setting -'
              puts "If your existing site content tends to have images or tables in your descriptions of items you'll probably want to keep these settings as they are."
              puts "However, if your content descriptions don't have much of these you will like want to change them to the opposite to take advantage of the improved Related Items interface placement."
              puts ''
              printed_related_items_notice = true
            end
          end

          SystemSetting.create!(setting_hash)
          p 'added ' + setting_hash['name']
        end
      end
    end

    desc 'Add the new default topics that are missing from our Kete installation.'
    task add_new_default_topics: :environment do
      topics_from_yml = YAML.load_file("#{RAILS_ROOT}/db/bootstrap/topics.yml")

      # support for legacy kete installations where basket ids
      # are different from those in topics.yml
      # NOTE: if this gets uses again in another task, move this to a reusable method of its own
      basket_ids = {
        '1' => 1,
        '2' => Basket::HELP_BASKET_ID,
        '3' => Basket::ABOUT_BASKET_ID,
        '4' => Basket::DOCUMENTATION_BASKET_ID,
      }

      # for each topic from yml
      topics_from_yml.each do |topic_array|
        topic_hash = topic_array[1]

        # if there are existing topics
        # drop id from hash, as we want to determine it dynamically
        # else we want to use the bootstap versions
        topic_hash.delete('id') if Topic.count > 0

        # map basket id to Kete's basket id (support for legacy installations)
        topic_hash['basket_id'] = basket_ids[topic_hash['basket_id'].to_s]

        # check if it's in the db by looking for a similar topic title in
        # the basket the topic is intended for, and if not present, add it
        if !Topic.find_by_title_and_basket_id(topic_hash['title'], topic_hash['basket_id'])
          topic = Topic.create!(topic_hash)
          topic.creator = User.first
          topic.save!
          p 'added topic: ' + topic_hash['title']
        end
      end
    end

    desc 'Add any new default baskets that are missing from our system.'
    task add_new_baskets: :environment do
      baskets_from_yml = YAML.load_file("#{RAILS_ROOT}/db/bootstrap/baskets.yml")
      # For each basket from yml
      # check if it's in the db
      # if not, add it
      # system settings have unique names
      admin_user = User.find(1)
      baskets_from_yml.each do |basket_array|
        basket_hash = basket_array[1]

        # drop id from hash, as we want to determine it dynamically
        basket_hash.delete('id')

        basket_id = 1 if basket_hash['urlified_name'] == 'site'
        basket_id ||= "#{basket_hash['urlified_name']}_basket".upcase.constantize
        if !Basket.find_by_id(basket_id)
          basket = Basket.create!(basket_hash)
          basket.accepts_role('admin', admin_user)
          p 'added ' + basket_hash['name']
        end
      end
    end

    desc 'Add tech_admin role if it is missing from our system.'
    task add_tech_admin: :environment do
      roles_from_yml = YAML.load_file("#{RAILS_ROOT}/db/bootstrap/roles.yml")

      admin_user = User.find(1)
      tech_admin_hash = roles_from_yml['tech_admin']
      if !Role.find_by_name('tech_admin')
        Role.create!(tech_admin_hash)
        admin_user.has_role('tech_admin', Basket.find(1))
        p 'added ' + tech_admin_hash['name']
      end
    end

    desc 'Change zebra password file to use clear text since encrypted is broken.'
    task change_zebra_password: :environment do
      ENV['ZEBRA_PASSWORD'] = ZoomDb.find(1).zoom_password
      Rake::Task['zebra:stop'].invoke
      Rake::Task['zebra:set_keteaccess'].invoke
      Rake::Task['zebra:start'].invoke
      p 'changed zebra password file'
    end

    desc 'This checks for missing required software and installs it if possible.'
    task check_required_software: :environment do
      include RequiredSoftware
      required_software = load_required_software
      missing_software = { 'Gems' => missing_libs(required_software), 'Commands' => missing_commands(required_software) }
      p "you have the following missing gems (you might want to do rake prep_app first): #{missing_software['Gems'].inspect}" if !missing_software['Gems'].blank?
      p "you have the following missing external software (take steps to install them before starting your kete server): #{missing_software['Commands'].inspect}" if !missing_software['Commands'].blank?
    end

    desc 'Fix the default baskets settings for unedited baskets so they inherit (like they were intended to)'
    task correct_basket_defaults: :environment do
      Basket.all.each do |basket|
        next unless Basket.standard_baskets.include?(basket.id)
        next unless basket.created_at == basket.updated_at

        correctable_fields = ['private_default', 'file_private_default', 'allow_non_member_comments', 'show_privacy_controls']
        current_basket_defaults = correctable_fields.map { |field| basket.send(field) }
        if basket.id == 1 # site basket
          standard_basket_defaults = [false, false, true, false]
        else # other default baskets
          standard_basket_defaults = [nil, nil, nil, nil]
        end

        next if current_basket_defaults == standard_basket_defaults

        correctable_fields.each_with_index do |field, index|
          basket.send(field + '=', standard_basket_defaults[index])
        end
        basket.save!
        p "Corrected settings of #{basket.name} basket"
      end
    end

    desc 'Make Site basket have membership requests closed, and member list visibility at least admin.'
    task set_default_join_and_memberlist_policies: :environment do
      # set some defaults in the site basket
      site_basket = Basket.first # site
      site_basket.set_setting(:basket_join_policy, 'closed') if site_basket.setting(:basket_join_policy).class == NilClass
      site_basket.setting(:memberlist_policy, 'at least admin') if site_basket.setting(:memberlist_policy).class == NilClass
      # if the about, help, or documentation baskets are nil, fill in the same value as the site basket
      Basket.about_basket.set_setting(:basket_join_policy, site_basket.setting(:basket_join_policy)) if Basket.about_basket.setting(:basket_join_policy).class == NilClass
      Basket.help_basket.set_setting(:basket_join_policy, site_basket.setting(:basket_join_policy)) if Basket.help_basket.setting(:basket_join_policy).class == NilClass
      Basket.documentation_basket.set_setting(:basket_join_policy, site_basket.setting(:basket_join_policy)) if Basket.documentation_basket.setting(:basket_join_policy).class == NilClass
    end

    desc 'Make all baskets with the status of NULL set to approved'
    task make_baskets_approved_if_status_null: :environment do
      Basket.all.each do |basket|
        basket.update_attributes!({
                                    status: 'approved',
                                    creator_id: 1
                                  }) if basket.status.blank?
      end
    end

    desc 'Make about, documentation, and help baskets ignore on the site basket recent topics if not done yet.'
    task ignore_default_baskets_if_setting_not_set: :environment do
      Basket.find_all_by_urlified_name(['about', 'documentation', 'help']).each do |basket|
        if basket.setting(:disable_site_recent_topics_display).class == NilClass
          basket.set_setting(:disable_site_recent_topics_display, true)
        end
      end
    end

    desc 'Ensure logins are valid before continuing (1.1 allowed spaces, 1.2 onwards does not).'
    task ensure_logins_all_valid: :environment do
      users = User.all.collect { |user| user.login =~ /\s/ ? user : nil }.compact.flatten
      users.each do |user|
        user.update_attributes!({ login: user.login.gsub(/\s/, '_') })
        UserNotifier.deliver_login_changed(user)
        p "Altered login of #{user.user_name}#{" (#{user.login})" if user.login != user.user_name}."
        # we should clear the contribution caches but we don't have access to this method here
        #   expire_contributions_caches_for(user)
      end
    end

    desc 'Transfer the old user names in the extended content fields into the display/resolved name fields on the users table, and remove the user name field mapping for Users'
    task move_user_name_to_display_and_resolved_name: :environment do
      user_count = 0
      User.find(:all, conditions: { resolved_name: '' }).each do |user|
        if user.display_name.blank?
          user_name_field = SystemSetting.extended_field_for_user_name
          extended_content_hash = user.xml_attributes_without_position
          if !extended_content_hash.blank? && !extended_content_hash[user_name_field].blank? && !extended_content_hash[user_name_field]['value'].blank?
            user.display_name = extended_content_hash[user_name_field]['value'].strip
            extended_content_hash = extended_content_hash.delete(user_name_field)
            user.extended_content_values = extended_content_hash
          end
        end
        user.resolved_name = user.login # this will get rewritten using an before save callback on the User model
        user.save!
        user_count += 1
      end
      # finally, lets removing the user name field mapping to prevent new user names from being set
      extended_field = ExtendedField.find_by_label('User Name')
      if extended_field
        content_type_id = ContentType.find_by_class_name('User').id
        extended_field_id = extended_field.id
        content_mapping = ContentTypeToFieldMapping.find_by_content_type_id_and_extended_field_id(content_type_id, extended_field_id)
        content_mapping.destroy unless content_mapping.nil?
      end
      p "#{user_count} users user_name moved to resolved_name" if user_count > 0
    end

    desc 'Give existing users a default locale if they don\'t already have one.'
    task set_default_locale_for_existing_users: :environment do
      User.update_all({ locale: 'en' }, { locale: nil })
    end

    desc 'Expire old style page caching for RSS feeds, otherwise they will conflict with new RSS caching system.'
    task expire_depreciated_rss_cache: :environment do
      # needed for zoom_class_controller method
      include ZoomControllerHelpers

      # this is overkill do fully every time upgrade
      # so check if the basket has been previously cached under public
      Basket.find(:all).each do |basket|
        path = RAILS_ROOT + '/public/' + basket.urlified_name
        if File.directory?(path)
          ZOOM_CLASSES.each do |zoom_class|
            # here's the hack way
            # we know RSS feed caches live under "all" or "for" directories
            # actually, just "all" most likely, but taking no chances
            ['all', 'for'].each do |subdir|
              full_path = path + '/' + subdir + '/' + zoom_class_controller(zoom_class)
              next unless File.directory?(full_path)
              # empty the directory files and then delete it
              Dir.glob("#{full_path}/*") do |file|
                File.delete(file)
              end
              Dir.rmdir(full_path)
            end

            # and here's the right way to do it
            # but i was getting this error:
            # private method `chomp' called for #<Hash:0x3e4e744>
            # /Users/walter/Development/apps/kete/vendor/rails/actionpack/lib/action_controller/caching/pages.rb:102:in `page_cache_file'
            # ApplicationController.expire_page(:controller => 'search',
            #                                               :action => 'rss',
            #                                               :urlified_name => basket.urlified_name,
            #                                               :controller_name_for_zoom_class => zoom_class_controller(zoom_class))
          end
          # finally delete the unneeded all and for directories
          ['all', 'for'].each do |subdir|
            full_path = path + '/' + subdir
            next unless File.directory?(full_path)
            Dir.rmdir(full_path)
          end
        end
      end
    end

    desc 'Make site basket default browse type blank, and other baskets inherit'
    task set_default_browse_type: :environment do
      # set some defaults in the site basket
      site_basket = Basket.first # site
      site_basket.set_setting(:browse_view_as, '') if site_basket.setting(:browse_view_as).class == NilClass
      # All other baskets inherit from site
      Basket.all.each do |basket|
        basket.set_setting(:browse_view_as, 'inherit') if basket.setting(:browse_view_as).class == NilClass
      end
    end

    desc 'Add basket id to taggings that dont have a basket id yet'
    task add_basket_id_to_taggings: :environment do
      puts 'Adding Basket ID to Tagging records'
      records = Tagging.all(conditions: { basket_id: nil })
      records.each do |tagging|
        item = tagging.taggable_type.constantize.find_by_id(tagging.taggable_id)
        tagging.update_attribute(:basket_id, item.basket_id) if item
      end
      puts "Added Basket ID to #{records.size} Taggings"
    end

    desc "Make all baskets have private item notification 'do not email' if setting doesn't exist"
    task make_baskets_private_notification_do_not_email: :environment do
      Basket.all.each do |basket|
        basket.set_setting(:private_item_notification, 'do_not_email') if basket.setting(:private_item_notification).blank?
      end
    end

    desc 'Add the parent_id, lft, and rgt values to comments that were created before acts_as_nested_set was put in place'
    task add_nested_values_to_comments: :environment do
      Comment.renumber_all if Comment.count(conditions: { lft: nil }) > 0
    end

    desc 'Migrate from older style related items inset booleans to newer related items position flags'
    task change_inset_to_position: :environment do
      # Use Model.update_all({ changes }, { :id => id }) to get
      # around time consuming validations and possible failures

      conditions = ['related_items_position IS NULL OR related_items_position IN (?)', ['', '0', '1']]
      topics = Topic::Version.all(conditions: conditions)
      topics.each do |topic|
        Topic::Version.update_all(
          {
            related_items_position: (topic.related_items_position.to_i == 1 ? 'inset' : 'below')
          }, { id: topic.id }
        )
      end

      topics = Topic.all(conditions: conditions)
      topics.each do |topic|
        Topic.update_all(
          {
            related_items_position: (topic.related_items_position.to_i == 1 ? 'inset' : 'below')
          }, { id: topic.id }
        )
      end

      topics = Topic.all(conditions: "private_version_serialized LIKE '%related_items_inset%'")
      topics.each do |topic|
        private_data = YAML.load(topic.private_version_serialized)
        private_data.each_with_index do |(key, value), index|
          next unless key == 'related_items_inset'
          private_data.delete_at(index)
          private_data << ['related_items_position', (value && value.to_i == 1 ? 'inset' : 'below')]
        end
        private_data = YAML.dump(private_data)
        Topic.update_all({ private_version_serialized: private_data }, { id: topic.id })
      end

      inset_default = SystemSetting.find_by_name('Related Items Inset Default')
      if inset_default
        position_default = SystemSetting.find_by_name('Related Items Position Default')
        position_default.update_attribute(:value, (inset_default.value.to_s == 'true' ? 'inset' : 'below'))
        inset_default.destroy
      end

      inset_hidden = SystemSetting.find_by_name('Hide Related Items Inset Field')
      if inset_hidden
        position_hidden = SystemSetting.find_by_name('Hide Related Items Position Field')
        position_hidden.update_attribute(:value, inset_default.value)
        inset_hidden.destroy
      end
    end

    desc 'Set all NULL value private_only values on topic type and content type field mappings to false.'
    task set_null_private_only_mappings_to_false: :environment do
      ContentTypeToFieldMapping.update_all({ private_only: false }, 'private_only IS NULL')
      TopicTypeToFieldMapping.update_all({ private_only: false }, 'private_only IS NULL')
    end

    desc 'Make all baskets import archive set functionality at least member.'
    task set_default_import_archive_set_policy: :environment do
      Basket.all.each do |basket|
        basket.setting(:import_archive_set_policy, 'at least admin') if basket.setting(:import_archive_set_policy).class == NilClass
      end
    end

    desc 'Add any default users that have not been added already.'
    task add_missing_users: :environment do
      users_from_yml = YAML.load(ERB.new(File.read("#{Rails.root}/db/bootstrap/users.yml")).result)

      # for each system_setting from yml
      # check if it's in the db
      # if not, add it
      # system settings have unique names
      users_from_yml.each do |setting_array|
        setting_hash = setting_array[1]

        # we have template values in the hash
        # get the values final form

        # if there are existing system settings
        # drop id from hash, as we want to determine it dynamically
        # else we want to use the bootstap versions
        setting_hash.delete('id') if User.count > 0

        setting_hash.delete('salt')
        setting_hash.delete('crypted_password')

        random_password = ActiveSupport::SecureRandom.hex(8)
        setting_hash[:password] = random_password
        setting_hash[:password_confirmation] = random_password
        setting_hash[:agree_to_terms] = '1'
        setting_hash[:security_code] = true
        setting_hash[:security_code_confirmation] = true

        if !User.find_by_login(setting_hash['login'])
          user = User.create!(setting_hash)
          user.has_role('member', Basket.first)

          p 'added ' + setting_hash['login']
        end
      end
    end

    desc 'Checks for mimetypes an adds them if needed.'
    task add_missing_mime_types: [
      'kete:upgrade:add_octet_stream_and_word_types',
      'kete:upgrade:add_excel_variants_to_documents',
      'kete:upgrade:add_aiff_to_audio_recordings',
      'kete:upgrade:add_tar_to_documents',
      'kete:upgrade:add_open_office_document_types',
      'kete:upgrade:add_jpegs_to_documents',
      'kete:upgrade:add_bmp_to_images',
      'kete:upgrade:add_eps_to_images',
      'kete:upgrade:add_psd_and_gimp_to_images_and_documents',
      'kete:upgrade:add_file_mime_type_variants']

    desc 'Adds psd variants if needed to images and documents'
    task add_psd_and_gimp_to_images_and_documents: :environment do
      ['Document Content Types', 'Image Content Types'].each do |setting_name|
        setting = SystemSetting.find_by_name(setting_name)
        ['image/vnd.adobe.photoshop', 'image/x-photoshop', 'application/x-photoshop', 'image/xcf'].each do |new_type|
          if setting.push(new_type)
            p "added #{new_type} mime type to " + setting.name
          end
        end
      end
    end

    desc 'Adds excel variants if needed'
    task add_excel_variants_to_documents: :environment do
      setting = SystemSetting.find_by_name('Document Content Types')
      ['application/excel', 'application/x-excel', 'application/x-msexcel'].each do |new_type|
        if setting.push(new_type)
          p "added #{new_type} mime type to " + setting.name
        end
      end
    end

    desc 'Adds application/octet-stream and application/word if needed'
    task add_octet_stream_and_word_types: :environment do
      ['Document Content Types', 'Video Content Types', 'Audio Content Types'].each do |setting_name|
        setting = SystemSetting.find_by_name(setting_name)
        if setting.push('application/octet-stream')
          p 'added octet stream mime type to ' + setting_name
        end
        if setting_name == 'Document Content Types'
          if setting.push('application/word')
            p 'added application/word mime type to ' + setting_name
          end
        end
      end
    end

    desc 'Adds application/x-tar if needed'
    task add_tar_to_documents: :environment do
      setting = SystemSetting.find_by_name('Document Content Types')
      if setting.push('application/x-tar')
        p 'added application/x-tar mime type to ' + setting.name
      end
    end

    desc 'Adds jpeg types to documents if needed.  A lot of archive and repository sites call scans to jpeg of a historical document pages.'
    task add_jpegs_to_documents: :environment do
      setting = SystemSetting.find_by_name('Document Content Types')
      ['image/jpeg', 'image/jpg'].each do |type|
        if setting.push(type)
          p "added #{type} mime type to " + setting.name
        end
      end
    end

    desc 'Adds audio/x-aiff if needed'
    task add_aiff_to_audio_recordings: :environment do
      setting = SystemSetting.find_by_name('Audio Content Types')
      if setting.push('audio/x-aiff')
        p 'added audio/x-aiff mime type to ' + setting.name
      end
    end

    desc 'Adds image/bmp if needed to images'
    task add_bmp_to_images: :environment do
      setting = SystemSetting.find_by_name('Image Content Types')
      if setting.push('image/bmp')
        p 'added image/bmp mime type to ' + setting.name
      end
    end

    desc 'Adds eps (application/postscript) if needed to images'
    task add_eps_to_images: :environment do
      setting = SystemSetting.find_by_name('Image Content Types')
      if setting.push('application/postscript')
        p 'added eps (application/postscript) mime type to ' + setting.name
      end
    end

    desc 'Adds OpenOffice document types if needed'
    task add_open_office_document_types: :environment do
      oo_types = [
        'application/vnd.oasis.opendocument.chart',
        'application/vnd.oasis.opendocument.database',
        'application/vnd.oasis.opendocument.formula',
        'application/vnd.oasis.opendocument.drawing',
        'application/vnd.oasis.opendocument.image',
        'application/vnd.oasis.opendocument.text-master',
        'application/vnd.oasis.opendocument.presentation',
        'application/vnd.oasis.opendocument.spreadsheet',
        'application/vnd.oasis.opendocument.text',
        'application/vnd.oasis.opendocument.text-web']

      setting = SystemSetting.find_by_name('Document Content Types')
      oo_types.each do |type|
        if setting.push(type)
          p "added #{type} mime type to " + setting.name
        end
      end
    end

    desc 'Adds File mime type variants'
    task add_file_mime_type_variants: :environment do
      new_mime_types = [
        ['Image Content Types', ['image/quicktime', 'image/x-quicktime', 'image/x-ms-bmp']],
        ['Document Content Types', ['application/x-zip', 'application/x-zip-compressed', 'application/x-compressed-tar', 'application/xml']],
        ['Video Content Types',    ['application/flash-video', 'application/x-flash-video', 'video/x-flv', 'video/mp4', 'video/x-m4v', 'video/ogg', 'application/ogg', 'video/theora']],
        ['Audio Content Types',    ['audio/mpg', 'audio/x-mpeg', 'audio/wav', 'audio/x-vorbis+ogg', 'audio/ogg', 'application/ogg', 'audio/vorbis', 'audio/speex', 'audio/flac']]
      ]
      new_mime_types.each do |settings|
        setting = SystemSetting.find_by_name(settings.first)
        settings.last.each do |type|
          if setting.push(type)
            p "added #{type} mime type to " + setting.name
          end
        end
      end
    end
  end
end
