From ae790c4ff9a6ed9df379e0360902d71dff855a54 Mon Sep 17 00:00:00 2001 From: Manuel Cillero Date: Wed, 6 Feb 2019 19:04:50 +0100 Subject: [PATCH] Nuevo plugin Redmine Wiki Lists 0.0.9 --- plugins/redmine_wiki_lists/GPL.txt | 339 ++++++++++++++++++ plugins/redmine_wiki_lists/README.rdoc | 19 + plugins/redmine_wiki_lists/init.rb | 16 + .../lib/redmine_wiki_lists.rb | 3 + .../lib/redmine_wiki_lists/issue_name_link.rb | 44 +++ .../lib/redmine_wiki_lists/ref_issues.rb | 225 ++++++++++++ .../redmine_wiki_lists/ref_issues/parser.rb | 297 +++++++++++++++ .../lib/redmine_wiki_lists/wiki_list.rb | 187 ++++++++++ 8 files changed, 1130 insertions(+) create mode 100755 plugins/redmine_wiki_lists/GPL.txt create mode 100755 plugins/redmine_wiki_lists/README.rdoc create mode 100755 plugins/redmine_wiki_lists/init.rb create mode 100755 plugins/redmine_wiki_lists/lib/redmine_wiki_lists.rb create mode 100755 plugins/redmine_wiki_lists/lib/redmine_wiki_lists/issue_name_link.rb create mode 100755 plugins/redmine_wiki_lists/lib/redmine_wiki_lists/ref_issues.rb create mode 100755 plugins/redmine_wiki_lists/lib/redmine_wiki_lists/ref_issues/parser.rb create mode 100755 plugins/redmine_wiki_lists/lib/redmine_wiki_lists/wiki_list.rb diff --git a/plugins/redmine_wiki_lists/GPL.txt b/plugins/redmine_wiki_lists/GPL.txt new file mode 100755 index 0000000..d511905 --- /dev/null +++ b/plugins/redmine_wiki_lists/GPL.txt @@ -0,0 +1,339 @@ + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Lesser General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program 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 2 of the License, or + (at your option) any later version. + + This program 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 this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + , 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. diff --git a/plugins/redmine_wiki_lists/README.rdoc b/plugins/redmine_wiki_lists/README.rdoc new file mode 100755 index 0000000..9f6e526 --- /dev/null +++ b/plugins/redmine_wiki_lists/README.rdoc @@ -0,0 +1,19 @@ += redmine_wiki_lists + +This plugin adds wiki macros that display issue lists. + +== Installation + +* Download zip file from https://github.com/tkusukawa/redmine_wiki_lists/releases +* Unzip +* Move the unziped folder into the $REDMINE/plugins/ +* Restart REDMINE + +== Usage + +http://www.r-labs.org/projects/wiki_lists/wiki/Wiki_Lists_en + +== Links + +redmine.org: http://www.redmine.org/plugins/redmine_wiki_lists + diff --git a/plugins/redmine_wiki_lists/init.rb b/plugins/redmine_wiki_lists/init.rb new file mode 100755 index 0000000..3bdd4e5 --- /dev/null +++ b/plugins/redmine_wiki_lists/init.rb @@ -0,0 +1,16 @@ +plugin_name = :redmine_wiki_lists + +Rails.configuration.to_prepare do + %w{issue_name_link ref_issues/parser ref_issues wiki_list}.each do |file_name| + require_dependency "#{plugin_name}/#{file_name}" + end +end + +Redmine::Plugin.register plugin_name do + name 'Redmine Wiki Lists plugin' + author 'Tomohisa Kusukawa' + description 'wiki macros to display lists of issues.' + version '0.0.9' + url 'https://www.r-labs.org/projects/wiki_lists/wiki/Wiki_Lists_en' + author_url 'https://github.com/tkusukawa/' +end diff --git a/plugins/redmine_wiki_lists/lib/redmine_wiki_lists.rb b/plugins/redmine_wiki_lists/lib/redmine_wiki_lists.rb new file mode 100755 index 0000000..5d23219 --- /dev/null +++ b/plugins/redmine_wiki_lists/lib/redmine_wiki_lists.rb @@ -0,0 +1,3 @@ +module RedmineWikiLists + # config will be here +end \ No newline at end of file diff --git a/plugins/redmine_wiki_lists/lib/redmine_wiki_lists/issue_name_link.rb b/plugins/redmine_wiki_lists/lib/redmine_wiki_lists/issue_name_link.rb new file mode 100755 index 0000000..b9f272f --- /dev/null +++ b/plugins/redmine_wiki_lists/lib/redmine_wiki_lists/issue_name_link.rb @@ -0,0 +1,44 @@ +module RedmineWikiLists::IssueNameLink + Redmine::WikiFormatting::Macros.register do + desc 'Make a link of a issue by its subject.' + macro :issue_name_link do |obj, args| + out = '' + + begin + raise '- no parameters' if args.count.zero? + raise '- too many parameters' if args.count > 1 + arg = args.shift + arg.strip! + + if arg =~ /\A([^:]*):([^:]*)\z/ + prj = Project.find_by_identifier($1) + prj ||= Project.find_by_name($1) + raise "- project:#{$1} is not found." unless prj + arg = $2 + else + prj = obj.project + end + + if arg =~ /\A([^\|]*)\|([^\|]*)\z/ + name = $1 + disp = $2 + else + name = arg + disp = arg + end + + issue = Issue.where(project_id: prj.id, subject: name).first + raise "- issue:#{name} is not found in prj:#{prj.to_s}" unless issue + Issue.find_by_subject(name) + out << link_to("#{disp}", issue_path(issue)) + rescue => err_msg + raise <<-TEXT.html_safe +Parameter error: #{err_msg}
+Usage: {{issue_name_link([project_name:]issue_subject[|description])}} +TEXT + end + + out.html_safe + end + end +end diff --git a/plugins/redmine_wiki_lists/lib/redmine_wiki_lists/ref_issues.rb b/plugins/redmine_wiki_lists/lib/redmine_wiki_lists/ref_issues.rb new file mode 100755 index 0000000..babb6fa --- /dev/null +++ b/plugins/redmine_wiki_lists/lib/redmine_wiki_lists/ref_issues.rb @@ -0,0 +1,225 @@ +module RedmineWikiLists::RefIssues + Redmine::WikiFormatting::Macros.register do + desc 'Displays a list of referer issues.' + macro :ref_issues do |obj, args| + parser = nil + + begin + parser = RedmineWikiLists::RefIssues::Parser.new(obj, args, @project) + rescue => err_msg + attributes = IssueQuery.available_columns + msg = <<-TEXT +-
parameter error: #{err_msg}
+#{err_msg.backtrace[0]}

+usage: {{ref_issues([option].., [column]..)}}
+
[options]
+-i=CustomQueryID : specify custom query by id
+-q=CustomQueryName : specify custom query by name
+-p[=identifier] : restrict project
+-f:FILTER[=WORD[|WORD...]] : additional filter
+-t[=column] : display text
+-l[=column] : display linked text
+-c : count issues
+-0 : no display if no issues +
[columns]
{ +TEXT + + while attributes + attributes[0...5].each do |a| + msg += a.name.to_s + ', ' + end + + attributes = attributes[5..-1] + msg += '
' if attributes + end + + msg += 'cf_* }
' + raise msg.html_safe + end + + begin + unless parser.has_search_conditions? # 検索条件がなにもなかったら + # 検索するキーワードを取得する + parser.search_words_w << parser.default_words(obj) + end + + @query = parser.query @project + + extend SortHelper + extend QueriesHelper + extend IssuesHelper + + sort_clear + sort_init(@query.sort_criteria.empty? ? [['id', 'desc']] : @query.sort_criteria) + sort_update(@query.sortable_columns) + #@issue_count_by_group = @query.issue_count_by_group + + parser.search_words_s.each do |words| + @query.add_filter('subject', '~', words) + end + + parser.search_words_d.each do |words| + @query.add_filter('description', '~', words) + end + + parser.search_words_w.each do |words| + @query.add_filter('subjectdescription', '~', words) + end + + models = + {'tracker' => Tracker, + 'category' => IssueCategory, + 'status' => IssueStatus, + 'assigned_to' => User, + 'author' => User, + 'version' => Version, + 'project' => Project} + ids = + {'tracker' => 'tracker_id', + 'category' => 'category_id', + 'status' => 'status_id', + 'assigned_to' => 'assigned_to_id', + 'author' => 'author_id', + 'version' => 'fixed_version_id', + 'project' => 'project_id'} + attributes = + {'tracker' => 'name', + 'category' => 'name', + 'status' => 'name', + 'assigned_to' => 'login', + 'author' => 'login', + 'version' => 'name', + 'project' => 'name'} + + parser.additional_filter.each do |filter_set| + filter = filter_set[:filter] + operator = filter_set[:operator] + values = filter_set[:values] + + if models.has_key?(filter) + unless values.nil? + tgt_objs = [] + values.each do |value| + tgt_obj = models[filter].find_by(attributes[filter]=>value) + raise "- can not resolve '#{value}' in #{models[filter].to_s}.#{attributes[filter]} " if tgt_obj.nil? + tgt_objs << tgt_obj.id.to_s + end + values = tgt_objs + end + filter = ids[filter] + end + + res = @query.add_filter(filter , operator, values) + + if res.nil? + filter_str = filter_set[:filter] + filter_set[:operator] + filter_set[:values].join('|') + cr_count = 0 + msg = "- failed add_filter: #{filter_str}

[FILTER]
" + + @query.available_filters.each do |k,f| + if cr_count >= 5 + msg += '
' + cr_count = 0 + end + + msg += k.to_s + ', ' + cr_count += 1 + end + + models.each do |k, _m| + if cr_count >= 5 + msg += '
' + cr_count = 0 + end + + msg += k.to_s + ', ' + cr_count += 1 + end + + msg += '

[OPERATOR]
' + cr_count = 0 + + Query.operators_labels.each do |k, l| + if cr_count >= 5 + msg += '
' + cr_count = 0 + end + + msg += k + ':' + l + ', ' + cr_count += 1 + end + + msg += '
' + raise msg.html_safe + end + end + + @query.column_names = parser.columns unless parser.columns.empty? + @issues = @query.issues(order: sort_clause) + + if parser.zero_flag && @issues.size == 0 + disp = '' + elsif parser.only_text || parser.only_link + disp = '' + atr = parser.only_text if parser.only_text + atr = parser.only_link if parser.only_link + word = nil + + @issues.each do |issue| + if issue.attributes.has_key?(atr) + word = issue.attributes[atr].to_s + else + issue.custom_field_values.each do |cf| + if 'cf_'+cf.custom_field.id.to_s == atr || cf.custom_field.name == atr + word = cf.value + end + end + end + + if word.nil? + msg = 'attributes:' + + issue.attributes.each do |a| + msg += a.to_s + ', ' + end + + raise msg.html_safe + break + end + + disp << ' ' if disp.size!=0 + + if parser.only_link + disp << link_to("#{word}", issue_path(issue)) + else + disp << textilizable(word, object: issue) + end + end + elsif parser.count_flag + disp = @issues.size.to_s + else + if params[:format] == 'pdf' + disp = render(partial: 'issues/list.html', locals: {issues: @issues, query: @query}) + else + if method(:context_menu).parameters.size > 0 + disp = context_menu(issues_context_menu_path) # < redmine 3.3.x + else + disp = context_menu.to_s # >= redmine 3.4.0 + end + disp << render(partial: 'issues/list', locals: {issues: @issues, query: @query}) + end + end + + disp.html_safe + rescue => err_msg + msg = "#{err_msg}" + if msg[0] != '-' + err_msg.backtrace.each do |backtrace| + msg << "
#{backtrace}" + end + end + raise msg.html_safe + end + end + end +end diff --git a/plugins/redmine_wiki_lists/lib/redmine_wiki_lists/ref_issues/parser.rb b/plugins/redmine_wiki_lists/lib/redmine_wiki_lists/ref_issues/parser.rb new file mode 100755 index 0000000..79cb0e1 --- /dev/null +++ b/plugins/redmine_wiki_lists/lib/redmine_wiki_lists/ref_issues/parser.rb @@ -0,0 +1,297 @@ +# To change this template, choose Tools | Templates +# and open the template in the editor. + +module RedmineWikiLists + module RefIssues + class Parser + attr_reader :search_words_s, :search_words_d, :search_words_w, :columns, + :custom_query_name, :custom_query_id, :additional_filter, :only_text, :only_link, :count_flag, :zero_flag + + def initialize(obj, args = nil, project = nil) + parse_args(obj, args, project) if args + end + + def parse_args(obj, args, project) + args ||= [] + @project = project + @search_words_s = [] + @search_words_d = [] + @search_words_w = [] + @columns = [] + @restrict_project = nil + @additional_filter = [] + @only_link = nil + @only_text = nil + @count_flag = nil + @zero_flag = nil + + args.each do |arg| + arg.strip! + arg.gsub!('>', '>') + arg.gsub!('<', '<') + + if arg=~/\A\-([^\=:]*)\s*([\=:])\s*(.*)\z/ + opt = $1.strip + sep = $2.strip + words = $3.strip + elsif arg=~/\A\-([^\=:]*)\z/ + opt = $1.strip + sep = nil + words = default_words(obj).join('|') + else + @columns << get_column(arg) + next + end + + case opt + when 's','sw','Dw','sDw','Dsw' + @search_words_s.push words_to_word_array(obj, words) + when 'd','dw','Sw','Sdw','dSw' + @search_words_d.push words_to_word_array(obj, words) + when 'w','sdw' + @search_words_w.push words_to_word_array(obj, words) + when 'q' + if sep + @custom_query_name = words + else + raise "- no CustomQuery name:#{arg}" + end + when 'i' + if sep + @custom_query_id = words + else + raise "- no CustomQuery ID:#{arg}" + end + when 'p' + @restrict_project = sep ? Project.find(words) : project + when 'f' + if sep + filter = '' + operator = '' + values = nil + + if words =~ /\A([^\s]*)\s+([^\s]*)\z/ + filter = $1 + operator = refer_field(obj, $2) + elsif words =~ /\A([^\s]*)\s+([^\s]*)\s+(.*)\z/ + filter = $1 + operator = refer_field(obj, $2) + values = words_to_word_array(obj, $3) + elsif words =~ /\A(.*)=(.*)\z/ + filter = $1 + operator = '=' + values = words_to_word_array(obj, $2) + else + filter = words + operator = '=' + values = default_words(obj) + end + + @additional_filter << {:filter=>filter, :operator=>operator, :values=>values} + else + raise "- no additional filter:#{arg}" + end + when 't' + @only_text = sep ? words : 'subject' + when 'l' + @only_link = sep ? words : 'subject' + when 'c' + @count_flag = true + when '0' + @zero_flag = true + else + raise "- unknown option:#{arg}" + end + end + end + + def has_search_conditions? + return true if @custom_query_id + return true if @custom_query_name + return true if @search_words_s.present? + return true if @search_words_d.present? + return true if @search_words_w.present? + return true if @additional_filter.present? + false + end + + def query(project) + # オプションにカスタムクエリがあればカスタムクエリを名前から取得 + if @custom_query_id + @query = IssueQuery.visible.find_by_id(@custom_query_id) + raise "- can not find CustomQuery ID:'#{@custom_query_id}'" unless @query + elsif @custom_query_name then + cond = 'project_id IS NULL' + cond << " OR project_id = #{project.id}" if project + cond = "(#{cond}) AND name = '#{@custom_query_name}'" + @query = IssueQuery.where(cond).where(user_id: User.current.id).first + @query = IssueQuery.where(cond).where(visibility: Query::VISIBILITY_PUBLIC).first unless @query + raise "- can not find CustomQuery Name:'#{@custom_query_name}'" unless @query + else + @query = IssueQuery.new(name: '_', filters: {}) + end + + @query.user = User.current + if @restrict_project + @query.project = @restrict_project + end + + # Queryモデルを拡張 + overwrite_sql_for_field(@query) + @query.available_filters['description'] = {type: :text, order: 8} + @query.available_filters['subjectdescription'] = {type: :text, order: 8} + @query.available_filters['fixed_version_id'] = {type: :int} + @query.available_filters['category_id'] = {type: :int} + @query.available_filters['parent_id'] = {type: :int} + @query.available_filters['id'] = {type: :int} + @query.available_filters['treated'] = {type: :date} + + @query + end + + def default_words(obj) + words = [] + + if obj.class == WikiContent # Wikiの場合はページ名および別名を検索ワードにする + words.push(obj.page.title) #ページ名 + redirects = WikiRedirect.where(redirects_to: obj.page.title) #別名query + + redirects.each do |redirect| + words << redirect.title #別名 + end + elsif obj.class == Issue # チケットの場合はチケットsubjectを検索ワードにする + words << obj.subject + elsif obj.class == Journal && obj.journalized_type == 'Issue' + # チケットコメントの場合もチケット番号表記を検索ワードにする + words << '#'+obj.journalized_id.to_s + end + + words + end + + private + + def get_column(name) + name_sym = name.to_sym + + IssueQuery.available_columns.each do |col| + return name_sym if name_sym == col.name.to_sym + end + + return :assigned_to if name_sym == :assigned + return :updated_on if name_sym == :updated + return :created_on if name_sym == :created + return name_sym if name =~ /\Acf_/ + raise "- unknown column:#{name}" + end + + # @todo Стремный патч, который сделан из-за отсутствия поминимания как работать с Query. По сути, надо патчить IssueQuery + def overwrite_sql_for_field(query) + def query.sql_for_field(field, operator, value, db_table, db_field, is_custom_filter=false) + if operator == '~' + # monkey patched for ref_issues: originally treat single value -> extend multiple value + if db_field=='subjectdescription' + sql = '(' + + value.each do |v| + sql << ' OR ' if sql != '(' + sql << "LOWER(#{db_table}.subject) LIKE '%#{self.class.connection.quote_string(v.to_s.downcase)}%'" + sql << " OR LOWER(#{db_table}.description) LIKE '%#{self.class.connection.quote_string(v.to_s.downcase)}%'" + end + + sql << ')' + return sql + else + sql = '(' + + value.each do |v| + sql << ' OR ' if sql != '(' + sql << "LOWER(#{db_table}.#{db_field}) LIKE '%#{self.class.connection.quote_string(v.to_s.downcase)}%'" + end + + sql << ')' + return sql + end + elsif operator == '==' + sql = '(' + + value.each do |v| + sql << ' OR ' if sql != '(' + sql << "LOWER(#{db_table}.#{db_field}) = '#{self.class.connection.quote_string(v.to_s.downcase)}'" + end + + sql << ')' + return sql + elsif db_field == 'treated' + raise "- too many values for treated" if value.length > 2 + raise "- too few values for treated" if value.length < 2 + start_date = value[0] + end_date = value[1] + if operator =~ /^\d+$/ + user = operator + else + user_obj = User.find_by_login(operator) + raise "- can not find user <#{operator}>" if user_obj.nil? + user = user_obj.id.to_s + end + + sql = '(' + sql << " (issues.author_id = #{user}" + sql << " AND (CAST(issues.created_on AS DATE) BETWEEN '#{start_date}' AND '#{end_date}'))" + sql << " OR (" + sql << " (select count(*) from journals where journalized_type = 'Issue' AND journalized_id = issues.id" + sql << " AND journals.user_id = #{user}" + sql << " AND (CAST(journals.created_on AS DATE) BETWEEN '#{start_date}' AND '#{end_date}')" + sql << " ) > 0" + sql << " )" + sql << ')' + return sql + end + + return super(field, operator, value, db_table, db_field, is_custom_filter) + end + end + + def words_to_word_array(obj, words) + words.split('|').collect do |word| + word.strip! + refer_field(obj, word) + end + end + + def refer_field(obj, word) + if word =~ /\[current_user\]/ + return User.current.login + end + + if word =~ /\[current_user_id\]/ + return User.current.id.to_s + end + + if word =~ /\[current_project_id\]/ + return @project.id.to_s + end + + if word =~ /\[(.*)days_ago\]/ + return (Date.today - $1.to_i).strftime("%Y-%m-%d") + end + + if word =~ /\A\[(.*)\]\z/ + raise "- can not use reference '#{word}' except for issues." if obj.class != Issue + atr = $1 + + if obj.attributes.has_key?(atr) + word = obj.attributes[atr] + else + obj.custom_field_values.each do |cf| + if 'cf_' + cf.custom_field.id.to_s == atr || cf.custom_field.name == atr + word = cf.value + end + end + end + end + return word.to_s + end + end + end +end diff --git a/plugins/redmine_wiki_lists/lib/redmine_wiki_lists/wiki_list.rb b/plugins/redmine_wiki_lists/lib/redmine_wiki_lists/wiki_list.rb new file mode 100755 index 0000000..63e31cc --- /dev/null +++ b/plugins/redmine_wiki_lists/lib/redmine_wiki_lists/wiki_list.rb @@ -0,0 +1,187 @@ +module RedmineWikiLists::WikiList + Redmine::WikiFormatting::Macros.register do + desc 'Displays a list of wiki pages with text elements (only inside wiki-pages).' + macro :wiki_list do |obj, args| + # 引数をパース + cond = '' + joins = '' + table_width = '' + column_keys = [] + column_names = [] + + begin + raise '- no parameters' if args.count.zero? + + args.each do |arg| + arg.strip! + + if arg =~ /\A\-([^\=]*)(\=.*)?\z/ # オプション表記発見 + case $1 + when 'c' # リストアップの対象を子ページに限定する場合 + cond << ' AND ' if cond != '' + cond << "parent_id = #{obj.page.id}" + when 'p' # リストアップの対象を特定の別プロジェクトのWikiに限定する場合 + if arg=~/\A[^\=]+\=(.*)\z/ then # プロジェクト名を指定 + name = $1.strip + prj = Project.find_by_name(name) + cond << ' AND ' if cond != '' + cond << "project_id = #{prj.id}" + else # プロジェクト名の指定が無い場合は当該WIKIのPJに限定 + cond << ' AND ' if cond != '' + cond << "project_id = #{obj.project.id}" + end + + joins << 'INNER JOIN wikis ON wiki_pages.wiki_id = wikis.id' + when 'w' # 表の横幅 + if arg =~ /\A[^\=]+\=(.*)\z/ # 幅を取得 + width = $1.strip + table_width = 'width="' + width + '"' + end + else + raise "- unknown option: #{arg}" + end + else # オプションでない場合はカラム指定 + if arg =~ /\A(.*)\|(.*)\|(.*)\z/ # 抽出キーワードと別にカラム表示名と列幅の指定がある場合 + column_keys.push($1.strip) + column_names.push($2.strip + '|' + $3.strip) + elsif arg =~ /\A(.*)\|(.*)\z/ # 抽出キーワードと別にカラム表示名の指定がある場合 + column_keys.push($1.strip) + column_names.push($2.strip) + else # カラム表示名の指定が無い場合は抽出キーワードをカラム表示名にする + column_keys.push(arg.strip) + column_names.push(arg.strip) + end + end + end + rescue => err_msg + msg = <<-TEXT +- parameter error: #{err_msg}
+usage: {{wiki_list([option]*,[column]*)}}
+[option]
+-c : search child pages
+-p=[PROJECT NAME] : restrict search pages by project
+-w=[WIDTH] : table width
+[column]
++title[| COLUMN_NAME] -> show page title
++alias[| COLUMN_NAME] -> show page aliases
+KEYWORD[| COLUMN_NAME] -> scan KEYWORD and show following words to EOL
+KEYWORD\\TERMINATOR[| COLUMN_NAME] -> scan KEYWORD and show following words to TERMINATOR +TEXT + raise msg.html_safe + end + + if column_keys.count.zero? + column_names.push 'title' + column_keys.push '+title' + end + + disp = "" + + # カラム名(最初の行)を作成 + column_names.each do |column_name| + if column_name =~ /\A(.*)\|(.*)\z/ then + disp << '' + else + disp << "" + end + end + + disp << '' + # Wikiページの抽出 + wiki_pages = WikiPage.joins(joins).where(cond) + + wiki_pages.each do |wiki_page| #---------------- Wikiページ毎の処理 + next unless wiki_page.visible? + # 1ページに抽出キーワードが複数あった場合に複数行表示するため一旦表示行を配列に記憶する + lines_by_page = [[]] # 最初は1ページ1行からスタート + column_num = 0 + + column_keys.each do |column_key| #---------------- カラム毎の処理 + case column_key + when '+title' # Wikiページ名 + html = + link_to(wiki_page.title, + controller: 'wiki', action: 'show', + project_id: wiki_page.project, id: wiki_page.title) + RedmineWikiLists::WikiList.set_lines(lines_by_page, column_num, html) + when '+alias' # Wikiページの別名 + redirects = WikiRedirect.where(wiki_id: wiki_page.wiki_id, redirects_to: wiki_page.title) + html = '' + + redirects.each do |redirect| + html << '
' if html.present? + html << redirect.title + end + + RedmineWikiLists::WikiList.set_lines(lines_by_page, column_num, html) + when '+project' # Wikiページのプロジェクト名 + RedmineWikiLists::WikiList.set_lines(lines_by_page, column_num, wiki_page.project.to_s) + else # それ以外はWikiページの中からキーワードで表示要素を抽出する + new_lines = [] # カラムキーワードが抽出される毎にこの変数に表示行を追加する + + if column_key =~ /\A(.*)\\(.*)\z/ + keyword = Regexp.escape($1.strip) + terminator = Regexp.escape($2.strip) + matches = wiki_page.text.scan /#{keyword}[\s\S]*?#{terminator}/ # キーワードから終端文字列までを抽出 + else + keyword = Regexp.escape(column_key) + terminator = false + matches = wiki_page.text.scan /#{keyword}.*\z/ # キーワードから行末までを抽出 + end + + matches.each do |match| # 抽出されたキーワード毎の処理 + # キーワードの後ろの文字列を抽出 + match =~ + if terminator + /\A#{keyword}([\s\S]*)#{terminator}/ + else + /\A#{keyword}(.*)\z/ + end + + if $1 + html = textilizable($1.strip) # 前後の空白を覗いてWiki表記解釈 + # Wikiページ内のこれまでのカラム処理で生成されたlinesに表示内容を記入 + RedmineWikiLists::WikiList.set_lines(lines_by_page, column_num, html) + + lines_by_page.each do |line| + new_lines.push(line.dup) # 本カラムによって生成される行にコピーを追加 + end + end + end + + if new_lines.length.zero? # キーワードが1つも抽出されていなかったら空文字を入れておく + RedmineWikiLists::WikiList.set_lines(lines_by_page, column_num, '') + else # 抽出があった場合は本カラムで作られた新しい表示行をページ表示行にする + lines_by_page=new_lines + end + end # case columnKey + + column_num += 1 + end # カラム毎の処理 + + # 配列に記憶されたページ内の表示内容をHTMLに吐き出す + lines_by_page.each do |line| + disp << '' + + line.each do |column| + disp << "" + end + + disp << '' + end + end # Wikiページ毎の処理 + + disp << '
' + $1 + '#{column_name}
#{column}
' + disp.html_safe + end + end + + # 配列の全ての要素配列のcolumn_num番目にstrを書きこむ + def set_lines(lines, column_num, str) + lines.each do |line| + line[column_num] = str + end + end + + module_function :set_lines +end