diff --git a/app/controllers/wiki_external_filter_controller.rb b/app/controllers/wiki_external_filter_controller.rb index 23c03dc..94a25bd 100644 --- a/app/controllers/wiki_external_filter_controller.rb +++ b/app/controllers/wiki_external_filter_controller.rb @@ -6,12 +6,14 @@ class WikiExternalFilterController < ApplicationController def filter name = params[:name] macro = params[:macro] + index = params[:index].to_i + filename = params[:filename] ? params[:filename] : name config = load_config cache_key = self.construct_cache_key(macro, name) content = read_fragment cache_key if (content) - send_data content, :type => config[macro]['content_type'], :disposition => 'inline' + send_data content[index], :type => config[macro]['content_type'], :disposition => 'inline', :filename => filename else render_404 end diff --git a/app/helpers/wiki_external_filter_helper.rb b/app/helpers/wiki_external_filter_helper.rb index 931b77a..431a93f 100644 --- a/app/helpers/wiki_external_filter_helper.rb +++ b/app/helpers/wiki_external_filter_helper.rb @@ -1,4 +1,5 @@ require 'digest/sha2' +require 'open4' module WikiExternalFilterHelper @@ -24,7 +25,7 @@ module WikiExternalFilterHelper ['wiki_external_filter', macro, name].join("/") end - def build(text, macro, info) + def build(text, attachments, macro, info) name = Digest::SHA256.hexdigest(text) result = {} @@ -49,14 +50,14 @@ module WikiExternalFilterHelper result[:content_type] = info['content_type'] RAILS_DEFAULT_LOGGER.debug "from cache: #{name}" else - result = self.build_forced(text, info) + result = self.build_forced(text, attachments, info) if result[:status] if expires > 0 write_fragment cache_key, result[:content], :expires_in => expires.seconds RAILS_DEFAULT_LOGGER.debug "cache saved: #{name}" end else - raise "Error applying external filter: #{result[:content]}" + raise "Error applying external filter: stdout is #{result[:content]}, stderr is #{result[:errors]}" end end @@ -66,23 +67,42 @@ module WikiExternalFilterHelper return result end - def build_forced(text, info) + def build_forced(text, attachments, info) + + if info['replace_attachments'] and attachments + attachments.each do |att| + text.gsub!(/#{att.filename.downcase}/i, att.diskfile) + end + end result = {} + content = [] + errors = "" - RAILS_DEFAULT_LOGGER.debug "executing command: #{info['command']}" + commands = info['commands']? info['commands'] : [info['command']] - content = IO.popen(info['command'], 'r+b') { |f| - f.write info[:prolog] if info.key?(:prolog) - f.write CGI.unescapeHTML(text) - f.write info[:epilog] if info.key?(:epilog) - f.close_write - f.read - } + commands.each do |command| + RAILS_DEFAULT_LOGGER.info "executing command: #{command}" - RAILS_DEFAULT_LOGGER.info("child status: sig=#{$?.termsig}, exit=#{$?.exitstatus}") + c = nil + e = nil + + Open4::popen4(command) { |pid, fin, fout, ferr| + fin.write info[:prolog] if info.key?(:prolog) + fin.write CGI.unescapeHTML(text) + fin.write info[:epilog] if info.key?(:epilog) + fin.close + c, e = [fout.read, ferr.read] + } + + RAILS_DEFAULT_LOGGER.debug("child status: sig=#{$?.termsig}, exit=#{$?.exitstatus}") + + content << c + errors += e if e + end result[:content] = content + result[:errors] = errors result[:content_type] = info['content_type'] result[:source] = text result[:status] = $?.exitstatus == 0 @@ -101,12 +121,12 @@ module WikiExternalFilterHelper end class Macro - def initialize(view, source, macro, info) + def initialize(view, source, attachments, macro, info) @view = view @view.controller.extend(WikiExternalFilterHelper) source.gsub!(/
/, "") source.gsub!(/<\/?p>/, "") - @result = @view.controller.build(source, macro, info) + @result = @view.controller.build(source, attachments, macro, info) end def render() diff --git a/app/views/wiki_external_filter/macro_block.html.erb b/app/views/wiki_external_filter/macro_block.html.erb index a72ab02..03f3949 100644 --- a/app/views/wiki_external_filter/macro_block.html.erb +++ b/app/views/wiki_external_filter/macro_block.html.erb @@ -12,6 +12,15 @@ when /\/.*(x|ht)ml/ then %>
<%= content %>
+<% + when /video\/x\-flv/ then + require 'RMagick' + image = Magick::Image::from_blob(content[0]).first +%> + 'filter', :macro => macro, :name => name, :index => 0) %>);background-repeat:no-repeat'><%= image_tag 'play_large.png', :plugin => 'wiki_external_filter', :style => "display:block;position:relative;left:#{image.columns / 2 - 83 / 2}px;top:#{image.rows / 2 - 83 / 2}px" %> +<% + @flowplayer_used = true +%> <% else %> @@ -24,6 +33,32 @@
Goto source: [[<%= wiki_name %>]] -<% content_for :header_tags do %> - <%= stylesheet_link_tag "wiki_external_filter.css", :plugin => "wiki_external_filter", :media => :all %> -<% end %> +<% + content_for :header_tags do +%> +<% + if not @stylesheets_included + @stylesheets_included = true +%> + <%= stylesheet_link_tag "wiki_external_filter.css", :plugin => "wiki_external_filter", :media => :all %> +<% + end +%> +<% + if @flowplayer_used + if not @flowplayer_scripts_included + @flowplayer_scripts_included = true +%> + <%= javascript_include_tag 'flowplayer.min.js', :plugin => 'wiki_external_filter' %> + +<% + end + end + end +%> diff --git a/app/views/wiki_external_filter/macro_inline.html.erb b/app/views/wiki_external_filter/macro_inline.html.erb index 32d1264..8dd3c2f 100644 --- a/app/views/wiki_external_filter/macro_inline.html.erb +++ b/app/views/wiki_external_filter/macro_inline.html.erb @@ -2,24 +2,60 @@ case content_type when /image\// then %> - 'filter', :macro => macro, :name => name) %>' alt="<%= h source %>" /> + 'filter', :macro => macro, :name => name, :index => 0) %>' alt="<%= h source %>" /> <% when /text\/plain/ then %> -
<%= h content %>
+
<%= h content[0] %>
<% when /\/.*(x|ht)ml/ then %> -
<%= content %>
+
<%= content[0] %>
+<% + when /video\/x\-flv/ then + require 'RMagick' + image = Magick::Image::from_blob(content[0]).first +%> + 'filter', :macro => macro, :name => name, :index => 0) %>);background-repeat:no-repeat'><%= image_tag 'play_large.png', :plugin => 'wiki_external_filter', :style => "display:block;position:relative;left:#{image.columns / 2 - 83 / 2}px;top:#{image.rows / 2 - 83 / 2}px" %> + +<% + @flowplayer_used = true +%> <% else %> - 'filter', :macro => macro, :name => name) %>' type='<%= content_type %>' title="<%= h source %>"> - 'filter', :macro => macro, :name => name) %>' type='<%= content_type %>' title="<%= h source %>" /> + 'filter', :macro => macro, :name => name, :index => 0) %>' type='<%= content_type %>' title="<%= h source %>"> + 'filter', :macro => macro, :name => name, :index => 0) %>' type='<%= content_type %>' title="<%= h source %>" /> <% end %> -<% content_for :header_tags do %> - <%= stylesheet_link_tag "wiki_external_filter.css", :plugin => "wiki_external_filter", :media => :all %> -<% end %> +<% + content_for :header_tags do +%> +<% + if not @stylesheets_included + @stylesheets_included = true +%> + <%= stylesheet_link_tag "wiki_external_filter.css", :plugin => "wiki_external_filter", :media => :all %> +<% + end +%> +<% + if @flowplayer_used + if not @flowplayer_scripts_included + @flowplayer_scripts_included = true +%> + <%= javascript_include_tag 'flowplayer.min.js', :plugin => 'wiki_external_filter' %> + +<% + end + end + end +%> diff --git a/assets/images/play_large.png b/assets/images/play_large.png new file mode 100644 index 0000000..247cf4b Binary files /dev/null and b/assets/images/play_large.png differ diff --git a/assets/javascripts/flowplayer.audio.swf b/assets/javascripts/flowplayer.audio.swf new file mode 100644 index 0000000..cdf93c7 Binary files /dev/null and b/assets/javascripts/flowplayer.audio.swf differ diff --git a/assets/javascripts/flowplayer.captions.swf b/assets/javascripts/flowplayer.captions.swf new file mode 100644 index 0000000..dba09e9 Binary files /dev/null and b/assets/javascripts/flowplayer.captions.swf differ diff --git a/assets/javascripts/flowplayer.controls.swf b/assets/javascripts/flowplayer.controls.swf new file mode 100644 index 0000000..aacdcd3 Binary files /dev/null and b/assets/javascripts/flowplayer.controls.swf differ diff --git a/assets/javascripts/flowplayer.min.js b/assets/javascripts/flowplayer.min.js new file mode 100644 index 0000000..e8fa742 --- /dev/null +++ b/assets/javascripts/flowplayer.min.js @@ -0,0 +1,24 @@ +/* + * flowplayer.js 3.1.4. The Flowplayer API + * + * Copyright 2009 Flowplayer Oy + * + * This file is part of Flowplayer. + * + * Flowplayer is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Flowplayer is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Flowplayer. If not, see . + * + * Date: 2009-02-25 21:24:29 +0000 (Wed, 25 Feb 2009) + * Revision: 166 + */ +(function(){function g(o){console.log("$f.fireEvent",[].slice.call(o))}function k(q){if(!q||typeof q!="object"){return q}var o=new q.constructor();for(var p in q){if(q.hasOwnProperty(p)){o[p]=k(q[p])}}return o}function m(t,q){if(!t){return}var o,p=0,r=t.length;if(r===undefined){for(o in t){if(q.call(t[o],o,t[o])===false){break}}}else{for(var s=t[0];p1){var r=arguments[1];var q=(arguments.length==3)?arguments[2]:{};if(typeof o=="string"){if(o.indexOf(".")!=-1){var t=[];m(n(o),function(){t.push(new b(this,k(r),k(q)))});return new d(t)}else{var s=c(o);return new b(s!==null?s:o,r,q)}}else{if(o){return new b(o,r,q)}}}return null};i(window.$f,{fireEvent:function(){var o=[].slice.call(arguments);var q=$f(o[0]);return q?q._fireEvent(o.slice(1)):null},addPlugin:function(o,p){b.prototype[o]=p;return $f},each:m,extend:i});if(typeof jQuery=="function"){jQuery.prototype.flowplayer=function(q,p){if(!arguments.length||typeof arguments[0]=="number"){var o=[];this.each(function(){var r=$f(this);if(r){o.push(r)}});return arguments.length?o[arguments[0]]:new d(o)}return this.each(function(){$f(this,k(q),p?k(p):{})})}}})();(function(){var e=typeof jQuery=="function";var i={width:"100%",height:"100%",allowfullscreen:true,allowscriptaccess:"always",quality:"high",version:null,onFail:null,expressInstall:null,w3c:false,cachebusting:false};if(e){jQuery.tools=jQuery.tools||{};jQuery.tools.flashembed={version:"1.0.4",conf:i}}function j(){if(c.done){return false}var l=document;if(l&&l.getElementsByTagName&&l.getElementById&&l.body){clearInterval(c.timer);c.timer=null;for(var k=0;k'}q.width=q.height=q.id=q.w3c=q.src=null;for(var l in q){if(q[l]!==null){n+=''}}var o="";if(t){for(var m in t){if(t[m]!==null){o+=m+"="+(typeof t[m]=="object"?g(t[m]):t[m])+"&"}}o=o.substring(0,o.length-1);n+='"}n+="";return n}function d(m,p,l){var k=flashembed.getVersion();f(this,{getContainer:function(){return m},getConf:function(){return p},getVersion:function(){return k},getFlashvars:function(){return l},getApi:function(){return m.firstChild},getHTML:function(){return a(p,l)}});var q=p.version;var r=p.expressInstall;var o=!q||flashembed.isSupported(q);if(o){p.onFail=p.version=p.expressInstall=null;m.innerHTML=a(p,l)}else{if(q&&r&&flashembed.isSupported([6,65])){f(p,{src:r});l={MMredirectURL:location.href,MMplayerType:"PlugIn",MMdoctitle:document.title};m.innerHTML=a(p,l)}else{if(m.innerHTML.replace(/\s/g,"")!==""){}else{m.innerHTML="

Flash version "+q+" or greater is required

"+(k[0]>0?"Your version is "+k:"You have no flash plugin installed")+"

"+(m.tagName=="A"?"

Click here to download latest version

":"

Download latest version from here

");if(m.tagName=="A"){m.onclick=function(){location.href="http://www.adobe.com/go/getflashplayer"}}}}}if(!o&&p.onFail){var n=p.onFail.call(this);if(typeof n=="string"){m.innerHTML=n}}if(document.all){window[p.id]=document.getElementById(p.id)}}window.flashembed=function(l,m,k){if(typeof l=="string"){var n=document.getElementById(l);if(n){l=n}else{c(function(){flashembed(l,m,k)});return}}if(!l){return}if(typeof m=="string"){m={src:m}}var o=f({},i);f(o,m);return new d(l,o,k)};f(window.flashembed,{getVersion:function(){var m=[0,0];if(navigator.plugins&&typeof navigator.plugins["Shockwave Flash"]=="object"){var l=navigator.plugins["Shockwave Flash"].description;if(typeof l!="undefined"){l=l.replace(/^.*\s+(\S+\s+\S+$)/,"$1");var n=parseInt(l.replace(/^(.*)\..*$/,"$1"),10);var r=/r/.test(l)?parseInt(l.replace(/^.*r(.*)$/,"$1"),10):0;m=[n,r]}}else{if(window.ActiveXObject){try{var p=new ActiveXObject("ShockwaveFlash.ShockwaveFlash.7")}catch(q){try{p=new ActiveXObject("ShockwaveFlash.ShockwaveFlash.6");m=[6,0];p.AllowScriptAccess="always"}catch(k){if(m[0]==6){return m}}try{p=new ActiveXObject("ShockwaveFlash.ShockwaveFlash")}catch(o){}}if(typeof p=="object"){l=p.GetVariable("$version");if(typeof l!="undefined"){l=l.replace(/^\S+\s+(.*)$/,"$1").split(",");m=[parseInt(l[0],10),parseInt(l[2],10)]}}}}return m},isSupported:function(k){var m=flashembed.getVersion();var l=(m[0]>k[0])||(m[0]==k[0]&&m[1]>=k[1]);return l},domReady:c,asString:g,getHTML:a});if(e){jQuery.fn.flashembed=function(l,k){var m=null;this.each(function(){m=flashembed(this,l,k)});return l.api===false?this:m}}})(); \ No newline at end of file diff --git a/assets/javascripts/flowplayer.rtmp.swf b/assets/javascripts/flowplayer.rtmp.swf new file mode 100644 index 0000000..fab819a Binary files /dev/null and b/assets/javascripts/flowplayer.rtmp.swf differ diff --git a/assets/javascripts/flowplayer.swf b/assets/javascripts/flowplayer.swf new file mode 100644 index 0000000..63f3934 Binary files /dev/null and b/assets/javascripts/flowplayer.swf differ diff --git a/config/routes.rb b/config/routes.rb new file mode 100644 index 0000000..545be92 --- /dev/null +++ b/config/routes.rb @@ -0,0 +1,3 @@ +ActionController::Routing::Routes.draw do |map| + map.connect 'wiki_external_filter/:filename', :controller => 'wiki_external_filter', :action => 'filter', :macro => 'flowplayer', :index => '1', :requirements => { :filename => /\S+\.flv/ } +end diff --git a/init.rb b/init.rb index 46d36a6..dab9f8e 100644 --- a/init.rb +++ b/init.rb @@ -18,8 +18,8 @@ Redmine::Plugin.register :wiki_external_filter do Redmine::WikiFormatting::Macros.register do info = config[name] desc info['description'] - macro name do |wiki_content_obj, args| - m = WikiExternalFilterHelper::Macro.new(self, args.to_s, name, info) + macro name do |obj, args| + m = WikiExternalFilterHelper::Macro.new(self, args.to_s, obj.page.attachments, name, info) m.render end @@ -33,7 +33,7 @@ Redmine::Plugin.register :wiki_external_filter do @included_wiki_pages ||= [] raise 'Circular inclusion detected' if @included_wiki_pages.include?(page.title) @included_wiki_pages << page.title - m = WikiExternalFilterHelper::Macro.new(self, page.content.text, name, info) + m = WikiExternalFilterHelper::Macro.new(self, page.content.text, page.attachments, name, info) @included_wiki_pages.pop m.render_block(args.to_s) end diff --git a/routes.rb b/routes.rb index c8d80e2..e030364 100644 --- a/routes.rb +++ b/routes.rb @@ -1,2 +1 @@ - -connect 'wiki_external_filter/:macro/:name', :controller => 'wiki_external_filter', :action => 'filter', :macro => /\S+/ +connect 'wiki_external_filter/:filename', :controller => 'wiki_external_filter', :action => 'filter', :macro => 'flowplayer', :index => '1', :requirements => { :filename => /\S+\.flv/ }