require "htmlentities" require "json" module Panda module CMS # # Bulk editor for site content in JSON format # class BulkEditor # # Export all site content to a JSON string # # @return [String] The JSON data # def self.export data = extract_current_data JSON.pretty_generate(data) end # # Import site content from a JSON string # # @param json_data [String] The JSON data to import # @return [Hash] A hash of debug information # def self.import(json_data) # See if we can parse the JSON new_data = JSON.parse(json_data) current_data = extract_current_data debug = { success: [], error: [], warning: [] } # Make sure templates are up to date Panda::CMS::Template.generate_missing_blocks # Run through the new data and compare it to the current data new_data["pages"].each do |path, page_data| if current_data["pages"][path].nil? begin page = Panda::CMS::Page.create!( path: path, title: page_data["title"], template: Panda::CMS::Template.find_by(name: page_data["template"]), parent: Panda::CMS::Page.find_by(path: page_data["parent"]) ) rescue => e debug[:error] << "Failed to create page '#{path}': #{e.message}" next end if !page debug[:error] << "Unhandled: page '#{path}' does not exist in the current data and cannot be created" next else debug[:success] << "Created page '#{path}' with title '#{page_data["title"]}'" end else page = Panda::CMS::Page.find_by(path: path) if page_data["title"] != current_data["pages"][path]["title"] page.update(title: page_data["title"]) debug[:success] << "Updated: page '#{path}' title from '#{current_data["pages"][path]["title"]}' to '#{page_data["title"]}'" end if page_data["template"] != current_data["pages"][path]["template"] # TODO: Handle page template changes debug[:error] << "Page '#{path}' template is '#{current_data["pages"][path]["template"]}' and cannot be changed to '#{page_data["template"]}' without manual intervention" end end page_data["contents"].each do |key, block_data| content = block_data["content"] if current_data.dig("pages", path, "contents", key).nil? raise "Unknown page 1" if page.nil? block = Panda::CMS::Block.find_or_create_by(key: key, template: page.template) do |block_meta| block_meta.name = key.titleize end if !block debug[:error] << "Error creating block '#{key.titleize}' on page '#{page.title}'" next end block_content = Panda::CMS::BlockContent.find_or_create_by(block: block, page: page) # block_content.content = HTMLEntities.new.encode(content, :named) block_content.content = content begin block_content.save! if block_content.content != content debug[:error] << "Failed to save content for '#{block.name}' on page '#{page.title}'" else debug[:success] << "Created '#{block.name}' content on page '#{page.title}'" end rescue => e debug[:error] << "Failed to create '#{block.name}' content on page '#{page.title}': #{e.message}" end elsif content != current_data["pages"][path]["contents"][key]["content"] # Content has changed raise "Unknown page 2" if page.nil? block = Panda::CMS::Block.find_by(key: key, template: page.template) if Panda::CMS::BlockContent.find_by(page: page, block: block)&.update(content: content) debug[:success] << "Updated '#{key.titleize}' content on page '#{page.title}'" else debug[:error] << "Failed to update '#{key.titleize}' content on page '#{page.title}'" end end end end new_data["menus"].each do |menu_data| end new_data["templates"].each do |template_data| end debug end # # Extract the current data from the database into a standardised format # # Used both as the export format, and to compare imported data with for changes # # @visibility private def self.extract_current_data data = { "pages" => {}, "menus" => {}, "templates" => {}, "settings" => {} } # Pages Panda::CMS::Page.includes(:template).order("lft ASC").each do |page| data["pages"][page.path] ||= {} end # TODO: Eventually set the position of the block in the template, and then order from there rather than the name? Panda::CMS::BlockContent.includes(:block, page: [:template]).order("panda_cms_pages.lft ASC, panda_cms_blocks.key ASC").each do |block_content| item = data["pages"][block_content.page.path] ||= {} item["title"] = block_content.page.title item["template"] = block_content.page.template.name item["parent"] = block_content.page.parent&.path item["contents"] ||= {} item["contents"][block_content.block.key] = { kind: block_content.block.kind, # We need the kind to recreate the block content: block_content.content } data["pages"][block_content.page.path] = item end # Menus # item = data["menus"][] ||= {} # Templates # item = data["templates"][] ||= {} data["settings"] = {} data.with_indifferent_access end end end end