# # == MasterView Admin Controller # # The MasterviewController is a Rails controller that you can enable # in your Rails application to provide administrative operations # for the MasterView template engine. # # To enable or disable the admin controller, use the # MasterView::Configuration setting +enable_admin_pages+ # to enable or disable its availability in your # config/masterview/settngs.rb or a suitable environment # settings file, according to your intended usage: # # config.enable_admin_pages = true # # When enabled, the MasterView admin controller is accessable # in your application at the address: # # http://yourapp.foo/masterview # # The controller places its stylesheets in a +masterview+ subdirectory # in your rails stylesheets directory (app/stylesheets/masterview). # # The MasterView admin controller is primarily a developer facility; # it is not generally appropriate for end-users of your rails application. # If you enable this facility in your production environment, # you should consider restricting access to authorized users. # #-- # ISSUE: we need to work through scenario of how mv admin controller fits # into overall scheme of one's rails application. How/when would developer # integrate stylesheets and layout into their overal app look? # How/when would access restriction actually be hooked up? # Standard technique is to do something along the lines of using # a before_filter which invokes things like :check_authentication # and :check_authorization services in the app framework # (or, say, predicate can_access? or has_permission? or...]. # But... this whole area of user authentication and authorization # has no specific conventions in the Rails ecosystem yet, # so we really can't make any assumptions yet about any particular # approach that MasterviewController itself could hook into. # We'll have to leave this as an exercise for the user and give # them tips on how to make it happen. # [DJL 17-Jun-2006] #++ # require 'masterview/extras/sample_templates' class MasterviewController < ApplicationController include MasterView::MIO::DefaultGenerateMIOFilter before_filter :check_authorization, :except => [ :access_not_allowed ] ###layout 'masterview_admin' MV_ADMIN_INSTALL_DIR = File.expand_path( File.join(File.dirname(__FILE__), '../..') ) #:nodoc: #CONTEXT_REF_VIEW = 'app/views' #CONTEXT_REF_LAYOUT = 'app/views/layout' FILE_LOC_APP = 'app' #:nodoc: FILE_LOC_MV = 'mv' #:nodoc: def index redirect_to :action => :list end # List the templates in the application and their status. # Provide operations to force rebuild (reparse and regenerate) # of specific templates or all templates and # create utility to ...mumble... # def list template_specs, content_hash = MasterView::TemplateSpec.scan @template_specs_sorted = template_specs.sort masterview_render_with_layout( 'masterview/admin/list', 'masterview_admin' ) end # Describe the MasterView configuration option settings def configuration masterview_render_with_layout( 'masterview/admin/configuration', 'masterview_admin_config' ) end def directives masterview_render_with_layout( 'masterview/admin/directives', 'masterview_admin_config' ) end def features masterview_render_with_layout( 'masterview/admin/features', 'masterview_admin_config' ) end # Rebuild all templates in the application. # Invoked from the main masterview admin page. def rebuild_all files_rebuilt = [] MasterView::TemplateSpec.scan do |template_spec, content_hash| if template_spec.status == MasterView::TemplateSpec::Status::ImportsOutdated && template_spec.rebuild_template(content_hash, :write_to_file => true) files_rebuilt << template_spec.path end end unless files_rebuilt.empty? flash[:notice] = files_rebuilt.join(', ')+' were updated' end redirect_to :action => :list end # Rebuild a specific template. # Invoked from the main masterview admin page. def rebuild path = params[:file] template_specs, content_hash = MasterView::TemplateSpec.scan template_spec = template_specs[path] raise 'Template '+path+' not found' unless template_spec if template_spec.rebuild_template(content_hash, :write_to_file => true) flash[:notice] = 'File '+path+' updated' else flash[:notice] = 'Identical content - no update needed for '+path end redirect_to :action => :list end # Create a new, empty template. # Invoked from the main masterview admin page. def create if @request.post? action_to_create = params[:action_name] src_file = params[:file] empty_file_path = find_path('app/views/masterview/admin/empty.rhtml') empty_insert_erb = File.readlines(empty_file_path).join dst_file = MasterView::TemplateSpec.create_empty_shell_for_action(src_file, action_to_create, empty_insert_erb, :write_to_file => true) flash[:notice] = dst_file+' was created' redirect_to :action => :list else smart_render 'masterview/admin/create' end end # View the generated rhtml def view_rhtml raise "View RHTML is disabled. Edit your config/masterview/settings.rb (or config/masterview/environments/xxxx.rb) file and set config.enable_view_rhtml = true. Restart application for change to take effect." unless MasterView::EnableMasterViewAdminViewRHTML @rhtml_file = params[:file] raise "RHTML file not specified" unless @rhtml_file f = MasterView::IOMgr.erb.path(@rhtml_file) raise "RHTML file ("+@rhtml_file+") not found. Maybe automatic parsing is disabled. You may invoke parsing manually by using rake mv:parse" unless f.exist? @rhtml_content = f.read smart_render 'masterview/admin/view_rhtml' end # interact, test template src, view results def interact @results = [] @src = params[:src] if @src begin sh_mio = MasterView::MIO::StringHashMIOTree.new({}, MasterView::ConfigSettings.generated_file_default_extension ) src_with_default_gen = add_default_gen_if_needed(@src) # apply a default generate MasterView::Parser.parse( src_with_default_gen, { :output_mio_tree => sh_mio, :omit_comment => true, :template_pathname => 'YOUR_TEMPLATE_PATH_HERE', :rescue_exceptions => false } ) @results = sh_mio.string_hash.sort.collect do |file,rhtml| os = OpenStruct.new os.file = file os.rhtml = rhtml os end rescue REXML::ParseException => e @error_message = e.to_s end end smart_render 'masterview/admin/interact' end def access_not_allowed #:nodoc: render :text => '
We\'re sorry, but the page you have requested is only available to authorized users.
', :status => 500 end protected # Default implementation of authorization check # to restrict access to administrative services def allow_access? # a more general solution might look something like: # current_user && user_has_perm?('mv-admin') # backstop: only allow for developer testing on local machine local_request? end # Check that the current user has authorization to access admin operations def check_authorization if ! allow_access? redirect_to :action => :access_not_allowed end end private # checks app path first for views and files, then falls back to files in MV def find_path(path) app_path = File.join(RAILS_ROOT, path) mv_path = File.join(MV_ADMIN_INSTALL_DIR, path) working_path = (File.exist?(app_path)) ? app_path : mv_path end # render local template file if exists otherwise render file from mv def smart_render(short_path) template_path, template_loc = resolve_relative_path( short_path, 'app/views' ) if template_loc == FILE_LOC_APP render :template => short_path else MasterView::Log.debug { 'render admin template from mv_path='+template_path } render :file => template_path end end # render local template file if exists otherwise render file from mv def masterview_render_with_layout(short_path, layout) #:nodoc: template_path, template_loc = resolve_relative_path( short_path, 'app/views' ) layout_path, layout_loc = resolve_relative_path( layout, 'app/views/layouts' ) if (template_loc == FILE_LOC_APP) && (layout_loc == FILE_LOC_APP) # app has customized both the admin page view and its layout render :template => short_path, :layout => layout else # template or layout or both use std masterview MasterView::Log.debug { 'render admin template from mv_path='+template_path } MasterView::Log.debug { 'render admin layout from mv_path='+layout_path } if layout_loc == FILE_LOC_MV masterview_render_file_with_layout( template_path, layout_path ) end end # Resolve a relative reference within the Rails application # or to the masterview installation directory. # Answer the full_path and a location indicator ('app' or 'mv') def resolve_relative_path( short_path, context_ref='app/views', file_ext='.rhtml' ) file_ref = "#{short_path}#{file_ext}" # resolve to rails app/views if present to allow app customization app_path = File.join(RAILS_ROOT, context_ref, file_ref) return [ app_path, FILE_LOC_APP ] if File.exist?(app_path) # resolve to standard masterview view template if not overridden mv_path = File.join(MV_ADMIN_INSTALL_DIR, context_ref, file_ref) if ! File.exist?(mv_path) MasterView::Log.error { '***NO SUCH MV ADMIN FILE: mv_path='+mv_path } end return [ mv_path, FILE_LOC_MV ] end end # patch a special version of template rendering into ActionController::Base # standard layout rendering is bolted down to app/views/layout context module ActionController #:nodoc: class Base #:nodoc: def masterview_render_file_with_layout( template_path, layout_path, local_assigns={} ) # Implementation based on ActionController::Layout#render_with_a_layout use_full_path = false #we already have full path, we don't want rails template_base prefixed add_variables_to_assigns content_for_layout = @template.render_file(template_path, use_full_path, local_assigns) # following was needed in ActionController::Layout because of the way it does its overrides #erase_render_results #??do we need to do this?? Is there any downside to doing so??! #add_variables_to_assigns @template.instance_variable_set('@content_for_layout', content_for_layout) render_text(@template.render_file(layout_path, use_full_path, local_assigns)) end end end