require 'json' require 'tempfile'

namespace :cul do

namespace :wp do
  namespace :migrate do

    desc "Copies the WordPress installation from one environment to another (e.g. prod to dev)"
    task :copy_from do
      require_cap_variables!([:wp_docroot])

      require_src_wp_domain_if_multisite!
      require_dest_wp_domain_if_multisite!

      set :src_wp_server, ask("source WordPress server (e.g. ldpd-nginx-prod1.cul.columbia.edu)")
      set :src_wp_docroot, ask("server path to source WordPress installation (to copy from)")

      puts "Checking #{fetch(:src_wp_server)} for version of WordPress instance at #{fetch(:src_wp_docroot)}..."

      # Check WP version on source WordPress instance. We require latest version because we always download the latest version for the new instance.
      failure = false
      on fetch(:remote_user) + '@' + fetch(:src_wp_server) do
        within fetch(:src_wp_docroot) do
          # Ensure that source WordPress is running the latest version
          result = capture :wp, (fetch(:multisite, false) ? "--url=#{fetch(:src_multisite_domain)}" : ''), 'core', 'check-update'
          if result.index('Success')
            puts "Running latest version of WordPress!"
          else
            puts 'Could not copy from source WordPress because it is not running the latest version of WordPress. Please update source before running a copy operation.'
            failure = true
          end
        end
      end
      next if failure # End if the previous checks failed

      # Run setup task to destroy current docroot and download new copy of WordPress
      # Note: The setup task enables maintenance mode during intermediate deployment
      # step, and then disables maintenance mode before it completes.
      invoke 'cul:wp:setup'

      # Enable maintenance mode AGAIN
      invoke! 'cul:wp:enable_maintenance_mode'

      # Run wp-content copy task to sync certain wp-content files (everything other than plugins, themes, mu-plugins) from src instance to dest instance
      invoke 'cul:wp:migrate:copy_wp_content'

      # Copy database
      invoke 'cul:wp:migrate:copy_database'

      # Run cul:wp:searchreplace one or more times
      invoke 'cul:wp:migrate:post_database_copy_searchreplace'

      # Download non-repo-managed plugins and themes
      invoke 'cul:wp:migrate:download_plugins_and_themes'

      # Disable maintenance mode
      invoke! 'cul:wp:disable_maintenance_mode'

      puts 'Copy complete!'
    end

    desc "Copies certain wp-content (everything other than plugins, themes, and mu-plugins) from one environment to another (e.g. prod to dev). This task is part of the :copy_from task and isn't meant to be called directly."
    task :copy_wp_content do
      # Define src and dest wp content paths, for later use
      src_wp_content_path = File.join(fetch(:src_wp_docroot), 'wp-content')
      dest_wp_content_path = File.join(fetch(:wp_docroot), 'wp-content')

      allowed_file_extensions = []

      # Get list of allowed file extensions to copy from source WP instance
      on fetch(:remote_user) + '@' + fetch(:src_wp_server) do
        within fetch(:src_wp_docroot) do
          allowed_file_extensions = JSON.parse(capture(:wp, (fetch(:multisite, false) ? "--url=#{fetch(:src_multisite_domain)}" : ''), 'eval', '"echo cul_allowed_upload_file_extensions_as_json();"'))
        end
      end

      # Copy certain files from src wp-content to dest wp-content
      on roles(:web) do
        within fetch(:wp_docroot) do

          # Note that because we have the '--copy-links' flag below, we're transforming all symlinks into real file copies
          rsync_base_params = [
            '--recursive',
            '--perms',
            '--times',
            '--devices',
            '--specials',
            '--copy-links',
            '--prune-empty-dirs',
            # Always exclude certain file and directory patterns
            '--exclude=".nfs*"',
            '--exclude="*.tmp.*"',
            '--exclude=".git*"',
            '--exclude=".svn*"',
            '--exclude=".hg*"'
          ]

          # First copy all files and folders OTHER THAN
          # plugins, themes, mu-plugins, uploads, and blogs.dir
          execute :rsync, (
            rsync_base_params + [
              # Apply exclusions, relative to the rsync src directory
              '--exclude="plugins"',
              '--exclude="mu-plugins"',
              '--exclude="themes"',
              '--exclude="uploads"',
              '--exclude="blogs.dir"'
            ] +
            # Apply user-defined exclusion filters, if present
            fetch(:wp_content_rsync_exclude_filters, []).map{ |filter_value| "--exclude=\"#{filter_value}\"" } +
            [
              # src directory
              fetch(:remote_user) + '@' + fetch(:src_wp_server) + ':' + src_wp_content_path + '/', # trailing slash so we only copy content within dir, but not dir itself
              # dest directory
              dest_wp_content_path
            ]
          )

          # Then copy all files and folders from uploads AND blogs.dir,
          # (with cul-allowed-upload-types file extension filter applied)
          execute :rsync, (
            rsync_base_params +
            # Apply user-defined exclusion filters, if present
            fetch(:wp_content_rsync_exclude_filters, []).map{ |filter_value| "--exclude=\"#{filter_value}\"" } +
            [
              # Apply inclusion filters, relative to the rsync src directory
              '--include="/uploads**/"',
              '--include="/blogs.dir**/"',
            ] +
            # Apply inclusion filters based on file extension
            # Generating case-insensitive rsync extension inclusion filters by doing this: --include="*.[Cc][Ss][Vv]"
            allowed_file_extensions.map{ |allowed_file_extension| "--include=\"*.[#{allowed_file_extension.split(//).map{|char| char.upcase + char.downcase }.join('][')}]\"" } +
            [
              # Exclude everything else not included by --include filters
              '--exclude="*"',
              # src directory
              fetch(:remote_user) + '@' + fetch(:src_wp_server) + ':' + src_wp_content_path + '/', # trailing slash so we only copy content within dir, but not dir itself
              # dest directory
              dest_wp_content_path
            ]
          )

        end
      end

      # Compare copied files and tell use which files were not copied
      puts 'Comparing file lists between environments...'

      on roles(:web) do
        within dest_wp_content_path do
          files_not_copied = capture(:comm, '-23',
            "<(ssh #{fetch(:remote_user)}@#{fetch(:src_wp_server)} \"cd #{src_wp_content_path} && find . -type f \\( -path '*/uploads/*' -o -path '*/blogs.dir/*' \\) | sort\")",
            "<(cd #{dest_wp_content_path} && find . -type f \\( -path '*/uploads/*' -o -path '*/blogs.dir/*' \\) | sort)"
          )

          # Generate list of files that weren't copied. Display this list to the user.
          puts (
            "The following files were not copied because of file and directory filters:\n" +
            "-------------------------\n" +
            "./plugins\n" +
            "./mu-plugins\n" +
            "./themes\n" +
            (files_not_copied.length > 0 ? files_not_copied + "\n" : '') +
            "-------------------------"
          )
        end
      end

    end

    desc "Copies certain wp-content (everything other than plugins, themes, and mu-plugins) from one environment to another (e.g. prod to dev). This task is part of the :copy_from task and isn't meant to be called directly."
    task :copy_database do

      db_export_tempfile = 'db_export_tempfile.sql'
      remote_server_db_export_tempfile_path = Dir::Tmpname.make_tmpname '/tmp/', db_export_tempfile
      local_server_db_export_file_path = File.join(fetch(:wp_docroot), db_export_tempfile)

      # Export database from source WordPress instance
      on fetch(:remote_user) + '@' + fetch(:src_wp_server) do
        within fetch(:src_wp_docroot) do
          puts 'Exporting database from source site. This might take a while for large sites...'
          # Export source WP DB to a temporary file
          execute :wp, (fetch(:multisite, false) ? "--url=#{fetch(:src_multisite_domain)}" : ''), 'db', 'export', remote_server_db_export_tempfile_path
        end
      end

      begin
        on roles(:web) do
          # Import database to destination WordPress instance
          within fetch(:wp_docroot) do
            puts 'Importing database. This may take a while for large sites...'

            # Copy database export from other server to this server
            execute :rsync,
              fetch(:remote_user) + '@' + fetch(:src_wp_server) + ':' + remote_server_db_export_tempfile_path,
              local_server_db_export_file_path

            # Drop all tables
            execute :wp, 'db', 'reset', '--yes'

            # Read in db file
            execute :wp, 'db', 'import', local_server_db_export_file_path
          end
        end
      ensure
        # Regardless of whether the db import was successful, make sure to
        # delete DB temp files on remote server and local server.

        # Delete on remote server
        on fetch(:remote_user) + '@' + fetch(:src_wp_server) do
          within fetch(:src_wp_docroot) do
            execute :rm, remote_server_db_export_tempfile_path
          end
        end

        # Delete on local server
        on roles(:web) do
          within fetch(:wp_docroot) do
            execute :rm, local_server_db_export_file_path
          end
        end
      end
    end

    desc "Runs one or more cul:wp:searchreplace operations after a database copy"
    task :post_database_copy_searchreplace do

      # Invoke searchreplace task to update URL
      puts "\nYou'll probably want to run the cul:wp:searchreplace command now, since it's likely that your WP URL differs between environments."
      if enter_y_to_continue(color_text("Do you want to run cul:wp:searchreplace?"))
        invoke! 'cul:wp:searchreplace'
        while enter_y_to_continue(color_text("Do you want to run cul:wp:searchreplace again?"))
          invoke! 'cul:wp:searchreplace'
        end
      end

    end

    desc "Gets a list of all non-mu plugins and themes on SOURCE WP instance and installs them to DESTINATION WP instance, but does not activate them. Activation status is already determined by the copied-over database."
    task :download_plugins_and_themes do

      data_for_plugins = []
      data_for_themes = []

      # Within src wp instance, get list of all plugins and themes with version
      on fetch(:remote_user) + '@' + fetch(:src_wp_server) do
        within File.join(fetch(:src_wp_docroot)) do
          data_for_plugins = JSON.parse(capture(:wp, (fetch(:multisite, false) ? "--url=#{fetch(:src_multisite_domain)}" : ''), 'plugin', 'list', '--fields=name,version,status', '--format=json'))
          data_for_themes = JSON.parse(capture(:wp, (fetch(:multisite, false) ? "--url=#{fetch(:src_multisite_domain)}" : ''), 'theme', 'list', '--fields=name,version,status', '--format=json'))
        end
      end

      # Within dest wp instance, install specifically versioned plugins and themes
      on roles(:web) do
        within File.join(fetch(:wp_docroot)) do
          # Get list of repo-managed plugins and themes so that we don't attempt to overwrite these directories
          repo_managed_plugin_names = fetch(:wp_custom_plugins, {}).keys
          repo_managed_theme_names = fetch(:wp_custom_themes, {}).keys

          puts "Downloading new copies of non-repo-managed plugins and themes..."

          data_for_plugins.delete_if{|plugin_info| repo_managed_plugin_names.include?(plugin_info['name']) }.each do |plugin_info|
            name = plugin_info['name']
            version = plugin_info['version']
            status = plugin_info['status']

            case status
            when 'active', 'active-network', 'inactive'
              execute :wp, (fetch(:multisite, false) ? "--url=#{fetch(:dest_multisite_domain)}" : ''), 'plugin', 'install', name, "--version=#{version}"
            when 'must-use'
              puts "--- WARNING: must-use plugin #{name} was not migrated over.  It should be put in your blog's repository and deployed through a regular deployment."
            end
          end

          data_for_themes.delete_if{|theme_info| repo_managed_theme_names.include?(theme_info['name']) }.each do |theme_info|
            name = theme_info['name']
            version = theme_info['version']
            status = theme_info['status']

            case status
            when 'active'
              execute :wp, (fetch(:multisite, false) ? "--url=#{fetch(:dest_multisite_domain)}" : ''), 'theme', 'install', name, "--version=#{version}", '--activate'
            when 'inactive', 'parent'
              execute :wp, (fetch(:multisite, false) ? "--url=#{fetch(:dest_multisite_domain)}" : ''), 'theme', 'install', name, "--version=#{version}"
            end
          end
        end
      end

    end

    desc 'Sets correct permissions for files in the WP docroot.'
    task :set_correct_wp_docroot_permissions do
      # Make wp-content readable and executable for "other" user so nginx, which runs as "nobody", can read wp-files.
      # Use -L flag because we want to follow symlinks. The deployment relies on symlinks.
      execute :find, '-L', File.join(fetch(:wp_docroot), 'wp-content'), '-type d -exec chmod o+rx "{}" \;'
      execute :find, '-L', File.join(fetch(:wp_docroot), 'wp-content'), '-type f -exec chmod o+r "{}" \;'
      # Make sure that wp-config.php is not world readable. It's only run by php, not nginx.
      execute :chmod, 'o-r', File.join(fetch(:wp_docroot), 'wp-config.php')
    end

  end
end

end