"Fossies" - the Fresh Open Source Software Archive

Member "redmine-4.1.1/lib/redmine/wiki_formatting/macros.rb" (6 Apr 2020, 12625 Bytes) of package /linux/www/redmine-4.1.1.tar.gz:


As a special service "Fossies" has tried to format the requested text file into HTML format (style: standard) with prefixed line numbers. Alternatively you can here view or download the uninterpreted source code file. See also the latest Fossies "Diffs" side-by-side code changes report for "macros.rb": 4.1.0_vs_4.1.1.

    1 # frozen_string_literal: true
    2 
    3 # Redmine - project management software
    4 # Copyright (C) 2006-2019  Jean-Philippe Lang
    5 #
    6 # This program is free software; you can redistribute it and/or
    7 # modify it under the terms of the GNU General Public License
    8 # as published by the Free Software Foundation; either version 2
    9 # of the License, or (at your option) any later version.
   10 #
   11 # This program is distributed in the hope that it will be useful,
   12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
   13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   14 # GNU General Public License for more details.
   15 #
   16 # You should have received a copy of the GNU General Public License
   17 # along with this program; if not, write to the Free Software
   18 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
   19 
   20 module Redmine
   21   module WikiFormatting
   22     module Macros
   23       module Definitions
   24         # Returns true if +name+ is the name of an existing macro
   25         def macro_exists?(name)
   26           Redmine::WikiFormatting::Macros.available_macros.key?(name.to_sym)
   27         end
   28 
   29         def exec_macro(name, obj, args, text, options={})
   30           macro_options = Redmine::WikiFormatting::Macros.available_macros[name.to_sym]
   31           return unless macro_options
   32 
   33           if options[:inline_attachments] == false
   34             Redmine::WikiFormatting::Macros.inline_attachments = false
   35           else
   36             Redmine::WikiFormatting::Macros.inline_attachments = true
   37           end
   38 
   39           method_name = "macro_#{name}"
   40           unless macro_options[:parse_args] == false
   41             args = args.split(',').map(&:strip)
   42           end
   43 
   44           begin
   45             if self.class.instance_method(method_name).arity == 3
   46               send(method_name, obj, args, text)
   47             elsif text
   48               raise "This macro does not accept a block of text"
   49             else
   50               send(method_name, obj, args)
   51             end
   52           rescue => e
   53             "<div class=\"flash error\">Error executing the <strong>#{h name}</strong> macro (#{h e.to_s})</div>".html_safe
   54           end
   55         end
   56 
   57         def extract_macro_options(args, *keys)
   58           options = {}
   59           while args.last.to_s.strip =~ %r{^(.+?)\=(.+)$} && keys.include?($1.downcase.to_sym)
   60             options[$1.downcase.to_sym] = $2
   61             args.pop
   62           end
   63           return [args, options]
   64         end
   65       end
   66 
   67       @@available_macros = {}
   68       @@inline_attachments = true
   69       mattr_accessor :available_macros
   70       mattr_accessor :inline_attachments
   71 
   72       class << self
   73         # Plugins can use this method to define new macros:
   74         #
   75         #   Redmine::WikiFormatting::Macros.register do
   76         #     desc "This is my macro"
   77         #     macro :my_macro do |obj, args|
   78         #       "My macro output"
   79         #     end
   80         #
   81         #     desc "This is my macro that accepts a block of text"
   82         #     macro :my_macro do |obj, args, text|
   83         #       "My macro output"
   84         #     end
   85         #   end
   86         def register(&block)
   87           class_eval(&block) if block_given?
   88         end
   89 
   90         # Defines a new macro with the given name, options and block.
   91         #
   92         # Options:
   93         # * :desc - A description of the macro
   94         # * :parse_args => false - Disables arguments parsing (the whole arguments
   95         #   string is passed to the macro)
   96         #
   97         # Macro blocks accept 2 or 3 arguments:
   98         # * obj: the object that is rendered (eg. an Issue, a WikiContent...)
   99         # * args: macro arguments
  100         # * text: the block of text given to the macro (should be present only if the
  101         #   macro accepts a block of text). text is a String or nil if the macro is
  102         #   invoked without a block of text.
  103         #
  104         # Examples:
  105         # By default, when the macro is invoked, the comma separated list of arguments
  106         # is split and passed to the macro block as an array. If no argument is given
  107         # the macro will be invoked with an empty array:
  108         #
  109         #   macro :my_macro do |obj, args|
  110         #     # args is an array
  111         #     # and this macro do not accept a block of text
  112         #   end
  113         #
  114         # You can disable arguments spliting with the :parse_args => false option. In
  115         # this case, the full string of arguments is passed to the macro:
  116         #
  117         #   macro :my_macro, :parse_args => false do |obj, args|
  118         #     # args is a string
  119         #   end
  120         #
  121         # Macro can optionally accept a block of text:
  122         #
  123         #   macro :my_macro do |obj, args, text|
  124         #     # this macro accepts a block of text
  125         #   end
  126         #
  127         # Macros are invoked in formatted text using double curly brackets. Arguments
  128         # must be enclosed in parenthesis if any. A new line after the macro name or the
  129         # arguments starts the block of text that will be passe to the macro (invoking
  130         # a macro that do not accept a block of text with some text will fail).
  131         # Examples:
  132         #
  133         #   No arguments:
  134         #   {{my_macro}}
  135         #
  136         #   With arguments:
  137         #   {{my_macro(arg1, arg2)}}
  138         #
  139         #   With a block of text:
  140         #   {{my_macro
  141         #   multiple lines
  142         #   of text
  143         #   }}
  144         #
  145         #   With arguments and a block of text
  146         #   {{my_macro(arg1, arg2)
  147         #   multiple lines
  148         #   of text
  149         #   }}
  150         #
  151         # If a block of text is given, the closing tag }} must be at the start of a new line.
  152         def macro(name, options={}, &block)
  153           options.assert_valid_keys(:desc, :parse_args)
  154           unless /\A\w+\z/.match?(name.to_s)
  155             raise "Invalid macro name: #{name} (only 0-9, A-Z, a-z and _ characters are accepted)"
  156           end
  157           unless block_given?
  158             raise "Can not create a macro without a block!"
  159           end
  160           name = name.to_s.downcase.to_sym
  161           available_macros[name] = {:desc => @@desc || ''}.merge(options)
  162           @@desc = nil
  163           Definitions.send :define_method, "macro_#{name}", &block
  164         end
  165 
  166         # Sets description for the next macro to be defined
  167         def desc(txt)
  168           @@desc = txt
  169         end
  170       end
  171 
  172       # Builtin macros
  173       desc "Sample macro."
  174       macro :hello_world do |obj, args, text|
  175         h("Hello world! Object: #{obj.class.name}, " +
  176           (args.empty? ? "Called with no argument" : "Arguments: #{args.join(', ')}") +
  177           " and " + (text.present? ? "a #{text.size} bytes long block of text." : "no block of text.")
  178         )
  179       end
  180 
  181       desc "Displays a list of all available macros, including description if available."
  182       macro :macro_list do |obj, args|
  183         out = ''.html_safe
  184         @@available_macros.each do |macro, options|
  185           out << content_tag('dt', content_tag('code', macro.to_s))
  186           out << content_tag('dd', content_tag('pre', options[:desc]))
  187         end
  188         content_tag('dl', out)
  189       end
  190 
  191       desc "Displays a list of child pages. With no argument, it displays the child pages of the current wiki page. Examples:\n\n" +
  192              "{{child_pages}} -- can be used from a wiki page only\n" +
  193              "{{child_pages(depth=2)}} -- display 2 levels nesting only\n" +
  194              "{{child_pages(Foo)}} -- lists all children of page Foo\n" +
  195              "{{child_pages(Foo, parent=1)}} -- same as above with a link to page Foo"
  196       macro :child_pages do |obj, args|
  197         args, options = extract_macro_options(args, :parent, :depth)
  198         options[:depth] = options[:depth].to_i if options[:depth].present?
  199 
  200         page = nil
  201         if args.size > 0
  202           page = Wiki.find_page(args.first.to_s, :project => @project)
  203         elsif obj.is_a?(WikiContent) || obj.is_a?(WikiContent::Version)
  204           page = obj.page
  205         else
  206           raise 'With no argument, this macro can be called from wiki pages only.'
  207         end
  208         raise 'Page not found' if page.nil? || !User.current.allowed_to?(:view_wiki_pages, page.wiki.project)
  209         pages = page.self_and_descendants(options[:depth]).group_by(&:parent_id)
  210         render_page_hierarchy(pages, options[:parent] ? page.parent_id : page.id)
  211       end
  212 
  213       desc "Includes a wiki page. Examples:\n\n" +
  214              "{{include(Foo)}}\n" +
  215              "{{include(projectname:Foo)}} -- to include a page of a specific project wiki"
  216       macro :include do |obj, args|
  217         page = Wiki.find_page(args.first.to_s, :project => @project)
  218         raise 'Page not found' if page.nil? || !User.current.allowed_to?(:view_wiki_pages, page.wiki.project)
  219         @included_wiki_pages ||= []
  220         raise 'Circular inclusion detected' if @included_wiki_pages.include?(page.id)
  221         @included_wiki_pages << page.id
  222         out = textilizable(page.content, :text, :attachments => page.attachments, :headings => false,  :inline_attachments => @@inline_attachments)
  223         @included_wiki_pages.pop
  224         out
  225       end
  226 
  227       desc "Inserts of collapsed block of text. Examples:\n\n" +
  228              "{{collapse\nThis is a block of text that is collapsed by default.\nIt can be expanded by clicking a link.\n}}\n\n" +
  229              "{{collapse(View details...)\nWith custom link text.\n}}"
  230       macro :collapse do |obj, args, text|
  231         html_id = "collapse-#{Redmine::Utils.random_hex(4)}"
  232         show_label = args[0] || l(:button_show)
  233         hide_label = args[1] || args[0] || l(:button_hide)
  234         js = "$('##{html_id}-show, ##{html_id}-hide').toggle(); $('##{html_id}').fadeToggle(150);"
  235         out = ''.html_safe
  236         out << link_to_function(show_label, js, :id => "#{html_id}-show", :class => 'icon icon-collapsed collapsible')
  237         out << link_to_function(hide_label, js, :id => "#{html_id}-hide", :class => 'icon icon-expended collapsible', :style => 'display:none;')
  238         out << content_tag('div', textilizable(text, :object => obj, :headings => false, :inline_attachments => @@inline_attachments), :id => html_id, :class => 'collapsed-text', :style => 'display:none;')
  239         out
  240       end
  241 
  242       desc "Displays a clickable thumbnail of an attached image.\n" +
  243              "Default size is 200 pixels. Examples:\n\n" +
  244              "{{thumbnail(image.png)}}\n" +
  245              "{{thumbnail(image.png, size=300, title=Thumbnail)}} -- with custom title and size"
  246       macro :thumbnail do |obj, args|
  247         args, options = extract_macro_options(args, :size, :title)
  248         filename = args.first
  249         raise 'Filename required' unless filename.present?
  250         size = options[:size]
  251         raise 'Invalid size parameter' unless size.nil? || /^\d+$/.match?(size)
  252         size = size.to_i
  253         size = 200 unless size > 0
  254         if obj && obj.respond_to?(:attachments) && attachment = Attachment.latest_attach(obj.attachments, filename)
  255           title = options[:title] || attachment.title
  256           thumbnail_url = url_for(:controller => 'attachments', :action => 'thumbnail', :id => attachment, :size => size, :only_path => @only_path)
  257           image_url = url_for(:controller => 'attachments', :action => 'show', :id => attachment, :only_path => @only_path)
  258 
  259           img = image_tag(thumbnail_url, :alt => attachment.filename)
  260           link_to(img, image_url, :class => 'thumbnail', :title => title)
  261         else
  262           raise "Attachment #{filename} not found"
  263         end
  264       end
  265 
  266       desc "Displays an issue link including additional information. Examples:\n\n" +
  267              "{{issue(123)}}                              -- Issue #123: Enhance macro capabilities\n" +
  268              "{{issue(123, project=true)}}                -- Andromeda - Issue #123: Enhance macro capabilities\n" +
  269              "{{issue(123, tracker=false)}}               -- #123: Enhance macro capabilities\n" +
  270              "{{issue(123, subject=false, project=true)}} -- Andromeda - Issue #123\n"
  271       macro :issue do |obj, args|
  272         args, options = extract_macro_options(args, :project, :subject, :tracker)
  273         id = args.first
  274         issue = Issue.visible.find_by(id: id)
  275 
  276         if issue
  277           # remove invalid options
  278           options.delete_if { |k,v| v != 'true' && v != 'false' }
  279 
  280           # turn string values into boolean
  281           options.each do |k, v|
  282             options[k] = v == 'true'
  283           end
  284 
  285           link_to_issue(issue, options)
  286         else
  287           # Fall back to regular issue link format to indicate, that there
  288           # should have been something.
  289           "##{id}"
  290         end
  291       end
  292     end
  293   end
  294 end