Commit 6b359969 authored by Francisco Juan's avatar Francisco Juan

Added all plugins required in ohwr.org

parent 8884f43b
Copyright (C) 2011 by Splendeo Innovación
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
== Redmine/Chiliproject Featured Projects Plugin
This plugin allows the redmine/chiliproject administrator to mark any project he wants as "featured" (notice that this is an "admin" right, not a project administrator right).
It also changes the redmine home page to show a "featured projects" box. This box replaces the default "Latest projects" box.
Finally, it adds two named scopes to Project: Project.featured and Project.not_featured
This plugin can be used in consonance with our Redmine Project Filtering plugin ( https://github.com/splendeo/redmine_project_filtering )
== Installation
1. Copy the plugin directory into the vendor/plugins directory
2. Migrate plugin:
rake db:migrate_plugins
3. Start Redmine
Installed plugins are listed and can be configured from 'Admin -> Plugins' screen.
== Credits
Development of this plugin was financed by the Open Hardware Repository - www.ohwr.org
<% content_for :header_tags do %>
<%= stylesheet_link_tag "featured_projects.css", :plugin => "featured_projects" %>
<% end %>
<% if User.current.allowed_to?(:update_featured_project_flags, nil, :global => true) %>
<p><%= form.check_box :is_featured %></p>
<% elsif project.is_featured? %>
<p><span class="icon icon-featured"><%= t('label_project_featured') %></span></p>
<% end %>
<h2><%= l(:label_home) %></h2>
<% content_for :header_tags do %>
<%= stylesheet_link_tag "featured_projects.css", :plugin => "featured_projects", :media => "screen" %>
<% end %>
<div class="splitcontentleft">
<div class="wiki"><%= textilizable Setting.welcome_text %></div>
<% if @news.any? %>
<div class="news box">
<h3><%=l(:label_news_latest)%></h3>
<%= render :partial => 'news/news', :collection => @news %>
<%= link_to l(:label_news_view_all), :controller => 'news' %>
</div>
<% end %>
<%= call_hook(:view_welcome_index_left, :projects => @projects) %>
</div>
<div class="splitcontentright">
<% if @projects.any? %>
<div class="box">
<h3 class="icon icon-featured"><%=l(:label_featured_projects)%></h3>
<ul>
<% for project in @projects %>
<% @project = project %>
<li>
<%= link_to_project project %> (<%= format_time(project.created_on) %>)
<%= textilizable project.short_description, :project => project %>
</li>
<% end %>
<% @project = nil %>
</ul>
</div>
<% end %>
<%= call_hook(:view_welcome_index_right, :projects => @projects) %>
</div>
<% content_for :header_tags do %>
<%= auto_discovery_link_tag(:atom, {:controller => 'news', :action => 'index', :key => User.current.rss_key, :format => 'atom'},
:title => "#{Setting.app_title}: #{l(:label_news_latest)}") %>
<%= auto_discovery_link_tag(:atom, {:controller => 'activities', :action => 'index', :key => User.current.rss_key, :format => 'atom'},
:title => "#{Setting.app_title}: #{l(:label_activity)}") %>
<% end %>
.icon-featured { background-image: url(../images/ribbon-16x16.png); }
# featured projects
en:
field_is_featured: Featured Project
label_featured_projects: Featured Projects
label_project_featured: This project is featured
# Sample plugin migration
# Use rake db:migrate_plugins to migrate installed plugins
class AddIsFeaturedToProjects < ActiveRecord::Migration
def self.up
add_column :projects, :is_featured, :boolean, :default => false, :null => false
end
def self.down
remove_column :accounts, :is_featured
end
end
# Redmine sample plugin
require 'redmine'
require 'dispatcher'
Dispatcher.to_prepare :featured_projects do
require_dependency 'featured_projects/hooks'
require_dependency 'project'
Project.send(:include, FeaturedProjects::Patches::ProjectPatch)
require_dependency 'welcome_controller'
unless WelcomeController.included_modules.include? FeaturedProjects::Patches::WelcomeControllerPatch
WelcomeController.send(:include, FeaturedProjects::Patches::WelcomeControllerPatch)
end
end
Redmine::Plugin.register :featured_projects do
name 'Featured Projects'
author 'Enrique García'
description 'Redmine featured projects'
version '0.0.1'
end
module FeaturedProjects
class Hooks < Redmine::Hook::ViewListener
# :project
# :form
render_on :view_projects_form, :partial => 'hooks/projects/featured_project_fields'
end
end
module FeaturedProjects
module Patches
module ProjectPatch
def self.included(base)
base.extend(ClassMethods)
base.send(:include, InstanceMethods)
base.class_eval do
unloadable # Send unloadable so it will not be unloaded in development
safe_attributes 'is_featured',
:if => lambda {|project, user| user.allowed_to?(:update_featured_project_flags, nil, :global => true) }
named_scope :featured, {:conditions => {:is_featured => true}}
named_scope :not_featured, { :conditions => ["#{Project.table_name}.is_featured <> :true", {:true => true}] }
end
end
module ClassMethods
end
module InstanceMethods
end
end
end
end
module FeaturedProjects
module Patches
module WelcomeControllerPatch
def self.included(base)
base.class_eval do
unloadable # Send unloadable so it will not be unloaded in development
base.send(:include, InstanceMethods)
base.class_eval do
alias_method_chain :index, :featured_projects
end
end
end
module InstanceMethods
def index_with_featured_projects
index_without_featured_projects
@projects = Project.visible.featured.all(:order => 'name ASC')
end
end
end
end
end
Redmine Piwik is a Redmine plugin to insert the Piwik
tracking code into Redmine based on user roles.
Copyright (C) 2009 Jörg Winter, B-Net1 Developments
Copyright (C) 2008 Eric Davis, Little Stream Software
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.
Thanks go to the following people for patches and contributions:
Maintainers
* Eric Davis of Little Stream Software
Contributors
* Gergő Jónás
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.
<one line to give the program's name and a brief idea of what it does.>
Copyright (C) <year> <name of author>
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.
<signature of Ty Coon>, 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.
= Redmine Piwik Plugins
Redmine plugin to insert the Piwik tracking code into Redmine based on user roles.
== Features
Adds your Piwik code to every pageview depending on your User roles; Anonymous user, Authenticated User, and Administrator.
== Installation and Setup
1. Download the plugin. There are three supported ways:
* Downloading the latest archive file from B-Net1 Developments projects (http://projekte.dev.b-net1.de/projects/bn1redminepiwik/files)
* Checkout the source from Subversion
svn co http://svn.dev.b-net1.de/bn1redminepiwik/trunk/piwik_plugin
* Install it using Rail's plugin installer
script/plugin install http://svn.dev.b-net1.de/bn1redminepiwik/trunk/piwik_plugin
2. Login to your Redmine install as an Administrator.
3. Configure your settings in Administration > Information > Configure
== License
This plugin is licensed under the GNU GPL v2. See COPYRIGHT.txt and GPL.txt for details.
== Project help
If you need help you can contact the maintainer at the Bug Tracker. The bug tracker is located at https://projekte.dev.b-net1.de/projects/bn1redminepiwik/issues
#!/usr/bin/env ruby
require 'redmine_plugin_support'
Dir[File.expand_path(File.dirname(__FILE__)) + "/lib/tasks/**/*.rake"].sort.each { |ext| load ext }
RedminePluginSupport::Base.setup do |plugin|
plugin.project_name = 'piwik_plugin'
plugin.default_task = [:spec]
end
begin
require 'jeweler'
Jeweler::Tasks.new do |s|
s.name = "piwik_plugin"
s.summary = "Redmine plugin to insert the Piwik tracking code into Redmine based on user roles."
s.email = "winter@b-net1.de"
s.homepage = "http://projekte.dev.b-net1.de/projects/bn1redminepiwik"
s.description = "Redmine plugin to insert the Piwik tracking code into Redmine based on user roles."
s.authors = ["Jörg Winter, Eric Davis"]
s.rubyforge_project = "piwik_plugin" # TODO
s.files = FileList[
"[A-Z]*",
"init.rb",
"rails/init.rb",
"{bin,generators,lib,test,app,assets,config,lang}/**/*",
'lib/jeweler/templates/.gitignore'
]
end
Jeweler::GemcutterTasks.new
Jeweler::RubyforgeTasks.new do |rubyforge|
rubyforge.doc_task = "rdoc"
end
rescue LoadError
puts "Jeweler, or one of its dependencies, is not available. Install it with: sudo gem install technicalpickles-jeweler -s http://gems.github.com"
end
<p>
<%= l(:piwik_directions) %>
</p>
<p>
<label><%= l(:piwik_log_url_label) %></label><%= text_field_tag 'settings[piwik_log_url]', @settings['piwik_log_url'] %>
</p>
<p>
<label><%= l(:piwik_log_id_label) %></label><%= text_field_tag 'settings[piwik_log_id]', @settings['piwik_log_id'], :size => 4, :maxlength => 5 %>
</p>
<p>
<label><%= l(:piwik_log_anonymous_label) %></label><%= check_box_tag 'settings[piwik_log_anonymous]', '1', @settings['piwik_log_anonymous'] %>
</p>
<p>
<label><%= l(:piwik_log_authenticated_label) %></label><%= check_box_tag 'settings[piwik_log_authenticated]', '1', @settings['piwik_log_authenticated'] %>
</p>
<p>
<label><%= l(:piwik_log_administrator_label) %></label><%= check_box_tag 'settings[piwik_log_administrator]', '1', @settings['piwik_log_administrator'] %>
</p>
de:
piwik_directions: Gib die URL deiner <a href="http://www.piwik.org">Piwik</a>-Installation ein, ebenso die Piwik-ID deiner Website <br />und wähle deine Log-Optionen.
piwik_log_url_label: URL der Piwik Installation
piwik_log_id_label: ID der Piwik Website
piwik_log_anonymous_label: Logge anonyme Benutzer
piwik_log_authenticated_label: Logge authentifizierte Benutzer
piwik_log_administrator_label: Logge Administratoren
en:
piwik_directions: Enter the URL of your <a href="http://www.piwik.org">Piwik</a> installation, the Piwik-ID of your Website <br />and choose your logging options.
piwik_log_url_label: URL of the Piwik installation
piwik_log_id_label: ID of the Piwik Website
piwik_log_anonymous_label: Log anonymous users
piwik_log_authenticated_label: Log authenticated user
piwik_log_administrator_label: Log administrator users
require File.dirname(__FILE__) + "/rails/init"
# German strings go here
piwik_directions: Gib die URL deiner <a href="http://www.piwik.org">Piwik</a>-Installation ein, ebenso die Piwik-ID deiner Website <br />und wähle deine Log-Optionen.
piwik_log_url_label: URL der Piwik Installation
piwik_log_id_label: ID der Piwik Website
piwik_log_anonymous_label: Logge anonyme Benutzer
piwik_log_authenticated_label: Logge authentifizierte Benutzer
piwik_log_administrator_label: Logge Administratoren
# English strings go here
piwik_directions: Enter the URL of your <a href="http://www.piwik.org">Piwik</a> installation, the Piwik-ID of your Website <br />and choose your logging options.
piwik_log_url_label: URL of the Piwik installation
piwik_log_id_label: ID of the Piwik Website
piwik_log_anonymous_label: Log anonymous users
piwik_log_authenticated_label: Log authenticated user
piwik_log_administrator_label: Log administrator users
# This class hooks into Redmine's View Listeners in order to
# add content to the page
class PiwikHooks < Redmine::Hook::ViewListener
# Adds the Piwik code to the layout if the current user meets
# the conditions setup by the System Administrator
def view_layouts_base_body_bottom(context = { })
log_anon = Setting.plugin_piwik_plugin['piwik_log_anonymous']
log_auth = Setting.plugin_piwik_plugin['piwik_log_authenticated']
log_admin = Setting.plugin_piwik_plugin['piwik_log_administrator']
log_url = Setting.plugin_piwik_plugin['piwik_log_url']
log_id = Setting.plugin_piwik_plugin['piwik_log_id']
if (!log_url.nil? && !log_id.nil?)
if (context[:request].env['HTTPS'] == 'on')
log_url = log_url.gsub(/http:|https:/, 'https:')
else
log_url = log_url.gsub(/http:|https:/, 'http:')
end
if (User.current.anonymous? && log_anon) || # Anon user
(User.current.logged? && log_auth && !User.current.admin?) || # Auth users who are not admins
(User.current.admin? && log_admin) # Admin user
output = "<!-- Piwik -->
<script type=\"text/javascript\" src=\"#{log_url}piwik.js\"></script>
<script type=\"text/javascript\">
try {
var piwikTracker = Piwik.getTracker(\"#{log_url}piwik.php\", #{log_id});
piwikTracker.trackPageView();
piwikTracker.enableLinkTracking();
} catch( err ) {}
</script><noscript><p><img src=\"#{log_url}piwik.php?idsite=#{log_id}\" style=\"border:0\" alt=\"\"/></p></noscript>
<!-- End Piwik Tag -->"
return output
else
return ''
end
else
return ''
end
end
end
# Generated by jeweler
# DO NOT EDIT THIS FILE
# Instead, edit Jeweler::Tasks in Rakefile, and run `rake gemspec`
# -*- encoding: utf-8 -*-
Gem::Specification.new do |s|
s.name = %q{piwik_plugin}
s.version = "0.1.1"
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
s.authors = ["Jörg Winter, Eric Davis"]
s.date = %q{2009-10-17}
s.description = %q{Redmine plugin to insert the Piwik tracking code into Redmine based on user roles.}
s.email = %q{winter@b-net1.de}
s.extra_rdoc_files = [
"README.rdoc"
]
s.files = [
"COPYRIGHT.txt",
"CREDITS.txt",
"GPL.txt",
"README.rdoc",
"Rakefile",
"VERSION",
"app/views/settings/_piwik_settings.rhtml",
"config/locales/de.yml",
"config/locales/en.yml",
"config/locales/hu.yml",
"config/locales/it.yml",
"init.rb",
"lang/de.yml",
"lang/en.yml",
"lang/hu.yml",
"lang/it.yml",
"lib/piwik_hooks.rb",
"rails/init.rb",
"test/test_helper.rb"
]
s.homepage = %q{http://projekte.dev.b-net1.de/projects/bn1redminepiwik}
s.rdoc_options = ["--charset=UTF-8"]
s.require_paths = ["lib"]
s.rubyforge_project = %q{piwik_plugin}
s.rubygems_version = %q{1.3.5}
s.summary = %q{Redmine plugin to insert the Piwik tracking code into Redmine based on user roles.}
s.test_files = [
"test/test_helper.rb"
]
if s.respond_to? :specification_version then
current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
s.specification_version = 3
if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
else
end
else
end
end
require 'redmine'
require_dependency 'piwik_hooks'
Redmine::Plugin.register :piwik_plugin do
name 'Piwik plugin'
author 'Jörg Winter'
description 'Redmine plugin to insert the Piwik tracking code into Redmine based on user roles.'
url 'http://projekte.dev.b-net1.de/projects/bn1redminepiwik'
author_url 'http://www.b-net1.de'
version '0.1.1'
requires_redmine :version_or_higher => '0.8.0'
settings :default => {
'piwik_log_url' => '',
'piwik_log_id' => '',
'piwik_log_anonymous' => true,
'piwik_log_authenticated' => true,
'piwik_log_administrator' => true
}, :partial => 'settings/piwik_settings'
end
# Load the normal Rails helper
require File.expand_path(File.dirname(__FILE__) + '/../../../../test/test_helper')
# Ensure that we are using the temporary fixture path
Engines::Testing.set_fixture_path
This Redmine plugin adds the environment name as class to the body of every HTML page.
\ No newline at end of file
require 'redmine'
require 'patches/application_helper_patch'
Redmine::Plugin.register :redmine_polls do
name 'Environmnet CSS'
author 'Francisco de Juan'
url 'http://development.splendeo.es/projects/redm-environment-css'
author_url 'http://www.splendeo.es'
description 'A plugin for adding the environment name as class to the body of every HTML page'
version '0.1'
end
require_dependency 'application_helper'
module ApplicationHelperPatch
def self.included(base) # :nodoc:
base.send(:include, InstanceMethods)
base.class_eval do
alias_method_chain :body_css_classes, :environment
end
end
module InstanceMethods
def body_css_classes_with_environment
"#{body_css_classes_without_environment} environment-#{Rails.env}"
end
end
end
ApplicationHelper.send(:include, ApplicationHelperPatch)
\ No newline at end of file
<?xml version="1.0" encoding="UTF-8"?>
<loadpath>
<pathentry path="" type="src"/>
<pathentry path="org.rubypeople.rdt.launching.RUBY_CONTAINER" type="con"/>
</loadpath>
<?xml version="1.0" encoding="UTF-8"?>
<projectDescription>
<name>redmine-gitolite</name>
<comment></comment>
<projects>
</projects>
<buildSpec>
<buildCommand>
<name>org.rubypeople.rdt.core.rubybuilder</name>
<arguments>
</arguments>
</buildCommand>
</buildSpec>
<natures>
<nature>org.rubypeople.rdt.core.rubynature</nature>
</natures>
</projectDescription>
= Patches
* work with gitolite
* support subproject git repo. (e.g. git@site.name:parent_project/subproject.git)
* fix bugs.
= Redmine Gitolite
A Redmine plugin which manages your gitolite configuration based on your projects and memberships in Redmine. Includes Public Key management views (extracted from http://plan.io).
This plugin was originally developed by Jan Schulz-Hofen for http://plan.io. Several updates/fixes were provided by github users untoldwind, tingar and ericpaulbishop. These updates were merged together and expanded upon by Eric Bishop.
In order to use this plugin you must have the following gems installed:
lockfile
inifile
net-ssh
== Copyright & License
Copyright (c) 2009-2010 Jan Schulz-Hofen, ROCKET RENTALS GmbH (http://www.rocket-rentals.de). MIT License.
Copyright (c) 2010 Eric Bishop (ericpaulbishop@gmail.com) MIT License.
class GitolitePublicKeysController < ApplicationController
unloadable
before_filter :require_login
before_filter :set_user_variable
before_filter :find_gitolite_public_key, :except => [:index, :new, :create]
def index
@status = if (session[:gitolite_public_key_filter_status]=params[:status]).nil?
GitolitePublicKey::STATUS_ACTIVE
elsif params[:status].blank?
nil
else
params[:status] == "true"
end
c = ARCondition.new(!@status.nil? ? ["active=?", @status] : nil)
@gitolite_public_keys = @user.gitolite_public_keys.all(:order => 'active DESC, created_at DESC', :conditions => c.conditions)
respond_to do |format|
format.html # index.html.erb
format.json { render :json => @gitolite_public_keys }
end
end
def edit
end
def update
if @gitolite_public_key.update_attributes(params[:public_key])
flash[:notice] = l(:notice_public_key_updated)
redirect_to url_for(:action => 'index', :status => session[:gitolite_public_key_filter_status])
else
render :action => 'edit'
end
end
def new
@gitolite_public_key = GitolitePublicKey.new(:user => @user)
end
def create
@gitolite_public_key = GitolitePublicKey.new(params[:public_key].merge(:user => @user))
if @gitolite_public_key.save
flash[:notice] = l(:notice_public_key_added)
redirect_to url_for(:action => 'index', :status => session[:gitolite_public_key_filter_status])
else
render :action => 'new'
end
end
def show
respond_to do |format|
format.html # show.html.erb
format.json { render :json => @gitolite_public_key }
end
end
protected
def set_user_variable
@user = User.current
end
def find_gitolite_public_key
key = GitolitePublicKey.find_by_id(params[:id])
if key and key.user == @user
@gitolite_public_key = key
elsif key
render_403
else
render_404
end
end
end
module GitolitePublicKeysHelper
def gitolite_public_keys_status_options_for_select(user, selected)
key_count_by_active = user.gitolite_public_keys.count(:group => 'active').to_hash
options_for_select([[l(:label_all), nil],
["#{l(:status_active)} (#{key_count_by_active[true].to_i})", GitolitePublicKey::STATUS_ACTIVE],
["#{l(:status_locked)} (#{key_count_by_active[false].to_i})", GitolitePublicKey::STATUS_LOCKED]], selected)
end
end
\ No newline at end of file
class GitoliteObserver < ActiveRecord::Observer
observe :project, :user, :gitolite_public_key, :member, :role, :repository
# def before_create(object)
# if object.is_a?(Project)
# repo = Repository::Git.new
# repo.url = repo.root_url = File.join(Gitolite::GITOSIS_BASE_PATH,"#{object.identifier}.git")
# object.repository = repo
# end
# end
def after_save(object) ; update_repositories(object) ; end
def after_destroy(object) ; update_repositories(object) ; end
protected
def update_repositories(object)
case object
when Repository then Gitolite::update_repositories(object.project)
when User then Gitolite::update_repositories(object.projects) unless is_login_save?(object)
when GitolitePublicKey then Gitolite::update_repositories(object.user.projects)
when Member then Gitolite::update_repositories(object.project)
when Role then Gitolite::update_repositories(object.members.map(&:project).uniq.compact)
end
end
private
# Test for the fingerprint of changes to the user model when the User actually logs in.
def is_login_save?(user)
user.changed? && user.changed.length == 2 && user.changed.include?("updated_on") && user.changed.include?("last_login_on")
end
end
class GitolitePublicKey < ActiveRecord::Base
STATUS_ACTIVE = 1
STATUS_LOCKED = 0
belongs_to :user
validates_uniqueness_of :title, :scope => :user_id
validates_uniqueness_of :identifier, :score => :user_id
validates_presence_of :title, :key, :identifier
named_scope :active, {:conditions => {:active => GitolitePublicKey::STATUS_ACTIVE}}
named_scope :inactive, {:conditions => {:active => GitolitePublicKey::STATUS_LOCKED}}
validate :has_not_been_changed
before_validation :set_identifier
def has_not_been_changed
unless new_record?
%w(identifier key user_id).each do |attribute|
errors.add(attribute, 'may not be changed') unless changes[attribute].blank?
end
end
end
def set_identifier
# TODO: some better naming, id is set long AFTER this method is called. Maybe timestamp?
self.identifier ||= "#{self.user.login.underscore}@#{self.title.underscore}".gsub(/[^0-9a-zA-Z-_@]/,'_')
end
def to_s ; title ; end
end
<div class="box tabular">
<p><%= f.text_field :title, :required => true %></p>
<p><%= f.text_area :key, :required => true, :disabled => !@gitolite_public_key.new_record?, :style => 'width:99%;height:140px;', :label => :field_public_key %>
<% if !@gitolite_public_key.new_record?%>
<br/><em><%= l(:label_key_cannot_be_changed_please_create_new_key) %></em>
<% end %>
</p>
<p><%= f.check_box :active %></p>
</div>
<h2><%= link_to l(:label_public_keys), public_keys_path %> &#187; <%= h @gitolite_public_key %></h2>
<%= error_messages_for :gitolite_public_key %>
<% form_for :public_key, @gitolite_public_key, :url => { :action => "update" }, :html => { :method => :put},
:builder => TabularFormBuilder,
:lang => current_language do |f| %>
<%= render :partial => 'form', :locals => { :f => f } %>
<%= submit_tag l(:button_save) %>
<% end %>
<% content_for :sidebar do %>
<%= render :partial => 'my/sidebar' %>
<% end %>
<% html_title(l(:label_public_keys)) -%>
<h2><%= link_to l(:label_my_account), :controller => 'my', :action => 'account' %> &#187; <%=l(:label_public_keys)%></h2>
<% form_tag({}, :method => :get) do %>
<fieldset><legend><%= l(:label_filter_plural) %></legend>
<label><%= l(:field_status) %>:</label>
<%= select_tag 'status', gitolite_public_keys_status_options_for_select(@user, @status), :class => "small", :onchange => "this.form.submit(); return false;" %>
<%= submit_tag l(:button_apply), :class => "small", :name => nil %>
</fieldset>
<% end %>
&nbsp;
<% if @gitolite_public_keys.any? %>
<table class="list">
<tr>
<th><%= l(:field_name) %></th>
<th><%= l(:field_created_on) %></th>
<th style="width:15%;"><%= l(:field_active) %></th>
<th align="center" style="width:10%;"> </th>
</tr>
<% @gitolite_public_keys.each do |key| %>
<tr class="<%= cycle('odd', 'even') %>">
<td><%= link_to h(key), :action => 'edit', :id => key %></td>
<td><%= format_time(key.created_at) %></td>
<td class="center" style="width:15%;"><%= image_tag('true.png') if key.active? %></td>
<td class="buttons">
<%= link_to l(key.active? ? :button_lock : :button_unlock), public_key_path(key, :public_key => {:active => key.active? ? GitolitePublicKey::STATUS_LOCKED : GitolitePublicKey::STATUS_ACTIVE}), :method => :put, :class => "icon #{key.active? ? 'icon-lock' : 'icon-unlock'}" %>
</td>
</tr>
<% end %>
</table>
<% end %>
<p><%= link_to l(:label_enumeration_new), { :action => 'new'} %></p>
<% html_title(l(:label_public_keys)) -%>
<h2><%= link_to l(:label_public_keys), public_keys_path %> &#187; <%=l(:label_public_key_new)%></h2>
<%= error_messages_for :gitolite_public_key %>
<% form_for :public_key, @gitolite_public_key, :url => { :action => "create" },
:builder => TabularFormBuilder,
:lang => current_language do |f| %>
<%= render :partial => 'form', :locals => { :f => f } %>
<%= submit_tag l(:button_create) %>
<% end %>
<% content_for :sidebar do %>
<%= render :partial => 'my/sidebar' %>
<% end %>
<% html_title(l(:label_public_keys)) -%>
<%# This is used to display basic git setup instructions, like on github... %>
<% flash.now[:error] = "Repository does not exist. Create one using the instructions below." %>
<div class="box">
<h2>Git Setup:</h2>
<pre> <a href="http://git-scm.com/download" target="_blank">Download and Install Git</a>
git config --global user.name "<%= User.current.login %>"
git config --global user.email <%= User.current.mail %>
</pre>
<h2>Permission Setup:</h2>
<pre> <%= link_to "Upload SSH Public Key", {:controller => 'my', :action => 'public_keys'} %>
Add yourself as a project developer: <%= link_to @project.name + " Settings", {:controller => 'projects', :action => 'settings'} %> -&gt; Members Tab -&gt; New Member
</pre>
<h2>Repository Setup:</h2>
<pre> mkdir <%= @project.identifier %>
cd <%= @project.identifier %>
git init
touch README
git add .
git commit -m 'Initializing <%= @project %> repository'
git remote add origin <%= Setting.plugin_redmine_gitolite['developerBaseUrls'] + Gitolite.repository_name(@project) + '.git' %>
git push origin master
</pre>
<h2>Existing Git Repo?</h2>
<pre> cd existing_git_repo
git remote add origin <%= Setting.plugin_redmine_gitolite['developerBaseUrls'] + Gitolite.repository_name(@project) + '.git' %>
git push origin master
</pre>
</div>
<div class="tabular settings">
<p>
<label><%= l(:label_gitolite_url)%></label>
<%= text_field_tag("settings[gitoliteUrl]", @settings['gitoliteUrl'], :size => 60) %>
<br />
</p>
<p>
<label><%= l(:label_gitolite_identity_file)%></label>
<%= text_field_tag("settings[gitoliteIdentityFile]", @settings['gitoliteIdentityFile'], :size => 60) %>
<br />
</p>
<p>
<label><%= l(:label_base_path)%></label>
<%= text_field_tag("settings[basePath]", @settings['basePath'], :size => 60) %>
<br />
</p>
<p>
<label><%= l(:label_developer_base_urls)%></label>
<%= text_area_tag("settings[developerBaseUrls]", @settings['developerBaseUrls'].split(/[\r\n\t ,;]+/).join("\n"), :size => 60) %>
<br />
</p>
<p>
<label><%= l(:label_read_only_base_urls)%></label>
<%= text_area_tag("settings[readOnlyBaseUrls]", @settings['readOnlyBaseUrls'].split(/[\r\n\t ,;]+/).join("\n"), :size => 60) %>
<br />
</p>
</div>
'bg':
label_public_keys: Public keys
label_public_key_new: New public key
label_gitolite_url: Gitolite URL
label_gitolite_identity_file: Gitolite identity file
label_base_path: Base path
label_developer_base_urls: Developer base URL(s)
label_read_only_base_urls: Read-only base URL(s)
field_public_key: Key
notice_public_key_updated: Public key was successfully updated.
notice_public_key_added: Public key was successfully added.
label_key_cannot_be_changed_please_create_new_key: 'The key cannot be altered anymore. However, you can deactivate it and create a new one.'
activerecord:
errors:
messages:
'may not be changed': 'may not be changed'
'bs':
label_public_keys: Public keys
label_public_key_new: New public key
label_gitolite_url: Gitolite URL
label_gitolite_identity_file: Gitolite identity file
label_base_path: Base path
label_developer_base_urls: Developer base URL(s)
label_read_only_base_urls: Read-only base URL(s)
field_public_key: Key
notice_public_key_updated: Public key was successfully updated.
notice_public_key_added: Public key was successfully added.
label_key_cannot_be_changed_please_create_new_key: 'The key cannot be altered anymore. However, you can deactivate it and create a new one.'
activerecord:
errors:
messages:
'may not be changed': 'may not be changed'
'ca':
label_public_keys: Public keys
label_public_key_new: New public key
label_gitolite_url: Gitolite URL
label_gitolite_identity_file: Gitolite identity file
label_base_path: Base path
label_developer_base_urls: Developer base URL(s)
label_read_only_base_urls: Read-only base URL(s)
field_public_key: Key
notice_public_key_updated: Public key was successfully updated.
notice_public_key_added: Public key was successfully added.
label_key_cannot_be_changed_please_create_new_key: 'The key cannot be altered anymore. However, you can deactivate it and create a new one.'
activerecord:
errors:
messages:
'may not be changed': 'may not be changed'
'cs':
label_public_keys: Public keys
label_public_key_new: New public key
label_gitolite_url: Gitolite URL
label_gitolite_identity_file: Gitolite identity file
label_base_path: Base path
label_developer_base_urls: Developer base URL(s)
label_read_only_base_urls: Read-only base URL(s)
field_public_key: Key
notice_public_key_updated: Public key was successfully updated.
notice_public_key_added: Public key was successfully added.
label_key_cannot_be_changed_please_create_new_key: 'The key cannot be altered anymore. However, you can deactivate it and create a new one.'
activerecord:
errors:
messages:
'may not be changed': 'may not be changed'
'da':
label_public_keys: Public keys
label_public_key_new: New public key
label_gitolite_url: Gitolite URL
label_gitolite_identity_file: Gitolite identity file
label_base_path: Base path
label_developer_base_urls: Developer base URL(s)
label_read_only_base_urls: Read-only base URL(s)
field_public_key: Key
notice_public_key_updated: Public key was successfully updated.
notice_public_key_added: Public key was successfully added.
label_key_cannot_be_changed_please_create_new_key: 'The key cannot be altered anymore. However, you can deactivate it and create a new one.'
activerecord:
errors:
messages:
'may not be changed': 'may not be changed'
'de':
label_public_keys: Öffentliche Schlüssel
label_public_key_new: Neuer öffentlicher Schlüssel
field_public_key: Schlüssel
notice_public_key_updated: Öffentlicher Schlüssel wurde erfolgreich aktualisiert.
notice_public_key_added: Öffentlicher Schlüssel wurde erfolgreich hinzugefügt.
label_gitolite_url: Gitolite URL
label_gitolite_identity_file: Gitolite identity file
label_base_path: Base path
label_developer_base_urls: Developer base URL(s)
label_read_only_base_urls: Read-only base URL(s)
label_key_cannot_be_changed_please_create_new_key: 'Der Schlüssel kann nicht mehr verändert werden. Sie können jedoch diesen Schlüssel deaktivieren und einen neuen anlegen.'
activerecord:
errors:
messages:
'may not be changed': "darf nicht verändert werden"
'el':
label_public_keys: Public keys
label_public_key_new: New public key
label_gitolite_url: Gitolite URL
label_gitolite_identity_file: Gitolite identity file
label_base_path: Base path
label_developer_base_urls: Developer base URL(s)
label_read_only_base_urls: Read-only base URL(s)
field_public_key: Key
notice_public_key_updated: Public key was successfully updated.
notice_public_key_added: Public key was successfully added.
label_key_cannot_be_changed_please_create_new_key: 'The key cannot be altered anymore. However, you can deactivate it and create a new one.'
activerecord:
errors:
messages:
'may not be changed': 'may not be changed'
'en':
label_public_keys: Public keys
label_public_key_new: New public key
label_gitolite_url: Gitolite URL
label_gitolite_identity_file: Gitolite identity file
label_base_path: Base path
label_developer_base_urls: Developer base URL(s)
label_read_only_base_urls: Read-only base URL(s)
field_public_key: Key
notice_public_key_updated: Public key was successfully updated.
notice_public_key_added: Public key was successfully added.
label_key_cannot_be_changed_please_create_new_key: 'The key cannot be altered anymore. However, you can deactivate it and create a new one.'
activerecord:
errors:
messages:
'may not be changed': 'may not be changed'
'es':
label_public_keys: Public keys
label_public_key_new: New public key
label_gitolite_url: Gitolite URL
label_gitolite_identity_file: Gitolite identity file
label_base_path: Base path
label_developer_base_urls: Developer base URL(s)
label_read_only_base_urls: Read-only base URL(s)
field_public_key: Key
notice_public_key_updated: Public key was successfully updated.
notice_public_key_added: Public key was successfully added.
label_key_cannot_be_changed_please_create_new_key: 'The key cannot be altered anymore. However, you can deactivate it and create a new one.'
activerecord:
errors:
messages:
'may not be changed': 'may not be changed'
'fi':
label_public_keys: Public keys
label_public_key_new: New public key
label_gitolite_url: Gitolite URL
label_gitolite_identity_file: Gitolite identity file
label_base_path: Base path
label_developer_base_urls: Developer base URL(s)
label_read_only_base_urls: Read-only base URL(s)
field_public_key: Key
notice_public_key_updated: Public key was successfully updated.
notice_public_key_added: Public key was successfully added.
label_key_cannot_be_changed_please_create_new_key: 'The key cannot be altered anymore. However, you can deactivate it and create a new one.'
activerecord:
errors:
messages:
'may not be changed': 'may not be changed'
'fr':
label_public_keys: Public keys
label_public_key_new: New public key
label_gitolite_url: Gitolite URL
label_gitolite_identity_file: Gitolite identity file
label_base_path: Base path
label_developer_base_urls: Developer base URL(s)
label_read_only_base_urls: Read-only base URL(s)
field_public_key: Key
notice_public_key_updated: Public key was successfully updated.
notice_public_key_added: Public key was successfully added.
label_key_cannot_be_changed_please_create_new_key: 'The key cannot be altered anymore. However, you can deactivate it and create a new one.'
activerecord:
errors:
messages:
'may not be changed': 'may not be changed'
'gl':
label_public_keys: Public keys
label_public_key_new: New public key
label_gitolite_url: Gitolite URL
label_gitolite_identity_file: Gitolite identity file
label_base_path: Base path
label_developer_base_urls: Developer base URL(s)
label_read_only_base_urls: Read-only base URL(s)
field_public_key: Key
notice_public_key_updated: Public key was successfully updated.
notice_public_key_added: Public key was successfully added.
label_key_cannot_be_changed_please_create_new_key: 'The key cannot be altered anymore. However, you can deactivate it and create a new one.'
activerecord:
errors:
messages:
'may not be changed': 'may not be changed'
'he':
label_public_keys: Public keys
label_public_key_new: New public key
label_gitolite_url: Gitolite URL
label_gitolite_identity_file: Gitolite identity file
label_base_path: Base path
label_developer_base_urls: Developer base URL(s)
label_read_only_base_urls: Read-only base URL(s)
field_public_key: Key
notice_public_key_updated: Public key was successfully updated.
notice_public_key_added: Public key was successfully added.
label_key_cannot_be_changed_please_create_new_key: 'The key cannot be altered anymore. However, you can deactivate it and create a new one.'
activerecord:
errors:
messages:
'may not be changed': 'may not be changed'
'hu':
label_public_keys: Public keys
label_public_key_new: New public key
label_gitolite_url: Gitolite URL
label_gitolite_identity_file: Gitolite identity file
label_base_path: Base path
label_developer_base_urls: Developer base URL(s)
label_read_only_base_urls: Read-only base URL(s)
field_public_key: Key
notice_public_key_updated: Public key was successfully updated.
notice_public_key_added: Public key was successfully added.
label_key_cannot_be_changed_please_create_new_key: 'The key cannot be altered anymore. However, you can deactivate it and create a new one.'
activerecord:
errors:
messages:
'may not be changed': 'may not be changed'
'id':
label_public_keys: Public keys
label_public_key_new: New public key
label_gitolite_url: Gitolite URL
label_gitolite_identity_file: Gitolite identity file
label_base_path: Base path
label_developer_base_urls: Developer base URL(s)
label_read_only_base_urls: Read-only base URL(s)
field_public_key: Key
notice_public_key_updated: Public key was successfully updated.
notice_public_key_added: Public key was successfully added.
label_key_cannot_be_changed_please_create_new_key: 'The key cannot be altered anymore. However, you can deactivate it and create a new one.'
activerecord:
errors:
messages:
'may not be changed': 'may not be changed'
'it':
label_public_keys: Chiavi pubbliche
label_public_key_new: Nuova chiave pubblica
label_gitolite_url: Gitolite URL
label_gitolite_identity_file: Gitolite identity file
label_base_path: Base path
label_developer_base_urls: Developer base URL(s)
label_read_only_base_urls: Read-only base URL(s)
field_public_key: Chiave
notice_public_key_updated: Chiave pubblica aggiornata.
notice_public_key_added: Chiave pubblica aggiunta.
label_key_cannot_be_changed_please_create_new_key: 'La chiave non può essere aggiornata. È comunque possibile disattivarla e crearne una nuova.'
activerecord:
errors:
messages:
'may not be changed': 'may not be changed'
'ja':
label_public_keys: Public keys
label_public_key_new: New public key
label_gitolite_url: Gitolite URL
label_gitolite_identity_file: Gitolite identity file
label_base_path: Base path
label_developer_base_urls: Developer base URL(s)
label_read_only_base_urls: Read-only base URL(s)
field_public_key: Key
notice_public_key_updated: Public key was successfully updated.
notice_public_key_added: Public key was successfully added.
label_key_cannot_be_changed_please_create_new_key: 'The key cannot be altered anymore. However, you can deactivate it and create a new one.'
activerecord:
errors:
messages:
'may not be changed': 'may not be changed'
'ko':
label_public_keys: Public keys
label_public_key_new: New public key
label_gitolite_url: Gitolite URL
label_gitolite_identity_file: Gitolite identity file
label_base_path: Base path
label_developer_base_urls: Developer base URL(s)
label_read_only_base_urls: Read-only base URL(s)
field_public_key: Key
notice_public_key_updated: Public key was successfully updated.
notice_public_key_added: Public key was successfully added.
label_key_cannot_be_changed_please_create_new_key: 'The key cannot be altered anymore. However, you can deactivate it and create a new one.'
activerecord:
errors:
messages:
'may not be changed': 'may not be changed'
'lt':
label_public_keys: Public keys
label_public_key_new: New public key
label_gitolite_url: Gitolite URL
label_gitolite_identity_file: Gitolite identity file
label_base_path: Base path
label_developer_base_urls: Developer base URL(s)
label_read_only_base_urls: Read-only base URL(s)
field_public_key: Key
notice_public_key_updated: Public key was successfully updated.
notice_public_key_added: Public key was successfully added.
label_key_cannot_be_changed_please_create_new_key: 'The key cannot be altered anymore. However, you can deactivate it and create a new one.'
activerecord:
errors:
messages:
'may not be changed': 'may not be changed'
'nl':
label_public_keys: Public keys
label_public_key_new: New public key
label_gitolite_url: Gitolite URL
label_gitolite_identity_file: Gitolite identity file
label_base_path: Base path
label_developer_base_urls: Developer base URL(s)
label_read_only_base_urls: Read-only base URL(s)
field_public_key: Key
notice_public_key_updated: Public key was successfully updated.
notice_public_key_added: Public key was successfully added.
label_key_cannot_be_changed_please_create_new_key: 'The key cannot be altered anymore. However, you can deactivate it and create a new one.'
activerecord:
errors:
messages:
'may not be changed': 'may not be changed'
'no':
label_public_keys: Public keys
label_public_key_new: New public key
label_gitolite_url: Gitolite URL
label_gitolite_identity_file: Gitolite identity file
label_base_path: Base path
label_developer_base_urls: Developer base URL(s)
label_read_only_base_urls: Read-only base URL(s)
field_public_key: Key
notice_public_key_updated: Public key was successfully updated.
notice_public_key_added: Public key was successfully added.
label_key_cannot_be_changed_please_create_new_key: 'The key cannot be altered anymore. However, you can deactivate it and create a new one.'
activerecord:
errors:
messages:
'may not be changed': 'may not be changed'
'pl':
label_public_keys: Public keys
label_public_key_new: New public key
label_gitolite_url: Gitolite URL
label_gitolite_identity_file: Gitolite identity file
label_base_path: Base path
label_developer_base_urls: Developer base URL(s)
label_read_only_base_urls: Read-only base URL(s)
field_public_key: Key
notice_public_key_updated: Public key was successfully updated.
notice_public_key_added: Public key was successfully added.
label_key_cannot_be_changed_please_create_new_key: 'The key cannot be altered anymore. However, you can deactivate it and create a new one.'
activerecord:
errors:
messages:
'may not be changed': 'may not be changed'
'pt-BR':
label_public_keys: Chaves pblicas
label_public_key_new: Nova chave pblica
label_gitolite_url: Gitolite URL
label_gitolite_identity_file: Arquivo de identificao do Gitolite
label_base_path: Caminho de base
label_developer_base_urls: URL(s) base de Desenvolvedor
label_read_only_base_urls: URL(s) base de Apenas-leitura
field_public_key: Chave
notice_public_key_updated: Chave pblica foi atualizada com sucesso.
notice_public_key_added: Chave pblica foi atualizada com sucesso.
label_key_cannot_be_changed_please_create_new_key: 'A chave no poder ser alterada. Mas voc ainda pode desativa-la e criar uma nova.'
activerecord:
errors:
messages:
'may not be changed': 'no dever ser alterado'
'pt':
label_public_keys: Public keys
label_public_key_new: New public key
label_gitolite_url: Gitolite URL
label_gitolite_identity_file: Gitolite identity file
label_base_path: Base path
label_developer_base_urls: Developer base URL(s)
label_read_only_base_urls: Read-only base URL(s)
field_public_key: Key
notice_public_key_updated: Public key was successfully updated.
notice_public_key_added: Public key was successfully added.
label_key_cannot_be_changed_please_create_new_key: 'The key cannot be altered anymore. However, you can deactivate it and create a new one.'
activerecord:
errors:
messages:
'may not be changed': 'may not be changed'
'ro':
label_public_keys: Public keys
label_public_key_new: New public key
label_gitolite_url: Gitolite URL
label_gitolite_identity_file: Gitolite identity file
label_base_path: Base path
label_developer_base_urls: Developer base URL(s)
label_read_only_base_urls: Read-only base URL(s)
field_public_key: Key
notice_public_key_updated: Public key was successfully updated.
notice_public_key_added: Public key was successfully added.
label_key_cannot_be_changed_please_create_new_key: 'The key cannot be altered anymore. However, you can deactivate it and create a new one.'
activerecord:
errors:
messages:
'may not be changed': 'may not be changed'
'ru':
label_public_keys: Public keys
label_public_key_new: New public key
label_gitolite_url: Gitolite URL
label_gitolite_identity_file: Gitolite identity file
label_base_path: Base path
label_developer_base_urls: Developer base URL(s)
label_read_only_base_urls: Read-only base URL(s)
field_public_key: Key
notice_public_key_updated: Public key was successfully updated.
notice_public_key_added: Public key was successfully added.
label_key_cannot_be_changed_please_create_new_key: 'The key cannot be altered anymore. However, you can deactivate it and create a new one.'
activerecord:
errors:
messages:
'may not be changed': 'may not be changed'
'sk':
label_public_keys: Public keys
label_public_key_new: New public key
label_gitolite_url: Gitolite URL
label_gitolite_identity_file: Gitolite identity file
label_base_path: Base path
label_developer_base_urls: Developer base URL(s)
label_read_only_base_urls: Read-only base URL(s)
field_public_key: Key
notice_public_key_updated: Public key was successfully updated.
notice_public_key_added: Public key was successfully added.
label_key_cannot_be_changed_please_create_new_key: 'The key cannot be altered anymore. However, you can deactivate it and create a new one.'
activerecord:
errors:
messages:
'may not be changed': 'may not be changed'
'sl':
label_public_keys: Public keys
label_public_key_new: New public key
label_gitolite_url: Gitolite URL
label_gitolite_identity_file: Gitolite identity file
label_base_path: Base path
label_developer_base_urls: Developer base URL(s)
label_read_only_base_urls: Read-only base URL(s)
field_public_key: Key
notice_public_key_updated: Public key was successfully updated.
notice_public_key_added: Public key was successfully added.
label_key_cannot_be_changed_please_create_new_key: 'The key cannot be altered anymore. However, you can deactivate it and create a new one.'
activerecord:
errors:
messages:
'may not be changed': 'may not be changed'
'sr':
label_public_keys: Public keys
label_public_key_new: New public key
label_gitolite_url: Gitolite URL
label_gitolite_identity_file: Gitolite identity file
label_base_path: Base path
label_developer_base_urls: Developer base URL(s)
label_read_only_base_urls: Read-only base URL(s)
field_public_key: Key
notice_public_key_updated: Public key was successfully updated.
notice_public_key_added: Public key was successfully added.
label_key_cannot_be_changed_please_create_new_key: 'The key cannot be altered anymore. However, you can deactivate it and create a new one.'
activerecord:
errors:
messages:
'may not be changed': 'may not be changed'
'sv':
label_public_keys: Public keys
label_public_key_new: New public key
label_gitolite_url: Gitolite URL
label_gitolite_identity_file: Gitolite identity file
label_base_path: Base path
label_developer_base_urls: Developer base URL(s)
label_read_only_base_urls: Read-only base URL(s)
field_public_key: Key
notice_public_key_updated: Public key was successfully updated.
notice_public_key_added: Public key was successfully added.
label_key_cannot_be_changed_please_create_new_key: 'The key cannot be altered anymore. However, you can deactivate it and create a new one.'
activerecord:
errors:
messages:
'may not be changed': 'may not be changed'
'th':
label_public_keys: Public keys
label_public_key_new: New public key
label_gitolite_url: Gitolite URL
label_gitolite_identity_file: Gitolite identity file
label_base_path: Base path
label_developer_base_urls: Developer base URL(s)
label_read_only_base_urls: Read-only base URL(s)
field_public_key: Key
notice_public_key_updated: Public key was successfully updated.
notice_public_key_added: Public key was successfully added.
label_key_cannot_be_changed_please_create_new_key: 'The key cannot be altered anymore. However, you can deactivate it and create a new one.'
activerecord:
errors:
messages:
'may not be changed': 'may not be changed'
'tr':
label_public_keys: Public keys
label_public_key_new: New public key
label_gitolite_url: Gitolite URL
label_gitolite_identity_file: Gitolite identity file
label_base_path: Base path
label_developer_base_urls: Developer base URL(s)
label_read_only_base_urls: Read-only base URL(s)
field_public_key: Key
notice_public_key_updated: Public key was successfully updated.
notice_public_key_added: Public key was successfully added.
label_key_cannot_be_changed_please_create_new_key: 'The key cannot be altered anymore. However, you can deactivate it and create a new one.'
activerecord:
errors:
messages:
'may not be changed': 'may not be changed'
'uk':
label_public_keys: Public keys
label_public_key_new: New public key
label_gitolite_url: Gitolite URL
label_gitolite_identity_file: Gitolite identity file
label_base_path: Base path
label_developer_base_urls: Developer base URL(s)
label_read_only_base_urls: Read-only base URL(s)
field_public_key: Key
notice_public_key_updated: Public key was successfully updated.
notice_public_key_added: Public key was successfully added.
label_key_cannot_be_changed_please_create_new_key: 'The key cannot be altered anymore. However, you can deactivate it and create a new one.'
activerecord:
errors:
messages:
'may not be changed': 'may not be changed'
'vi':
label_public_keys: Public keys
label_public_key_new: New public key
label_gitolite_url: Gitolite URL
label_gitolite_identity_file: Gitolite identity file
label_base_path: Base path
label_developer_base_urls: Developer base URL(s)
label_read_only_base_urls: Read-only base URL(s)
field_public_key: Key
notice_public_key_updated: Public key was successfully updated.
notice_public_key_added: Public key was successfully added.
label_key_cannot_be_changed_please_create_new_key: 'The key cannot be altered anymore. However, you can deactivate it and create a new one.'
activerecord:
errors:
messages:
'may not be changed': 'may not be changed'
'zh-TW':
label_public_keys: Public keys
label_public_key_new: New public key
label_gitolite_url: Gitolite URL
label_gitolite_identity_file: Gitolite identity file
label_base_path: Base path
label_developer_base_urls: Developer base URL(s)
label_read_only_base_urls: Read-only base URL(s)
field_public_key: Key
notice_public_key_updated: Public key was successfully updated.
notice_public_key_added: Public key was successfully added.
label_key_cannot_be_changed_please_create_new_key: 'The key cannot be altered anymore. However, you can deactivate it and create a new one.'
activerecord:
errors:
messages:
'may not be changed': 'may not be changed'
'zh':
label_public_keys: Public keys
label_public_key_new: New public key
label_gitolite_url: Gitolite URL
label_gitolite_identity_file: Gitolite identity file
label_base_path: Base path
label_developer_base_urls: Developer base URL(s)
label_read_only_base_urls: Read-only base URL(s)
field_public_key: Key
notice_public_key_updated: Public key was successfully updated.
notice_public_key_added: Public key was successfully added.
label_key_cannot_be_changed_please_create_new_key: 'The key cannot be altered anymore. However, you can deactivate it and create a new one.'
activerecord:
errors:
messages:
'may not be changed': 'may not be changed'
if defined? map
map.resources :public_keys, :controller => 'gitolite_public_keys', :path_prefix => 'my'
else
ActionController::Routing::Routes.draw do |map|
map.resources :public_keys, :controller => 'gitolite_public_keys', :path_prefix => 'my'
end
end
\ No newline at end of file
class CreateGitolitePublicKeys < ActiveRecord::Migration
def self.up
create_table :gitolite_public_keys do |t|
t.column :title, :string
t.column :identifier, :string
t.column :key, :text
t.column :active, :integer, :default => 1
t.references :user
t.timestamps
end
end
def self.down
drop_table :gitolite_public_keys
end
end
require 'redmine'
require_dependency 'principal'
require_dependency 'user'
require_dependency 'gitolite'
require_dependency 'gitolite/patches/repositories_controller_patch'
require_dependency 'gitolite/patches/repositories_helper_patch'
require_dependency 'gitolite/patches/git_adapter_patch'
Redmine::Plugin.register :redmine_gitolite do
name 'Redmine Gitolite plugin'
author 'Christian Käser, Zsolt Parragi, Yunsang Choi, Joshua Hogendorn, Jan Schulz-Hofen and others'
description 'Enables Redmine to update a gitolite server.'
version '0.1.0'
settings :default => {
'gitoliteUrl' => 'git@localhost:gitolite-admin.git',
'gitoliteIdentityFile' => '/srv/projects/redmine/miner/.ssh/id_rsa',
'developerBaseUrls' => 'git@www.salamander-linux.com:,https://[user]@www.salamander-linux.com/git/',
'readOnlyBaseUrls' => 'http://www.salamander-linux.com/git/',
'basePath' => '/srv/projects/git/repositories/',
},
:partial => 'redmine_gitolite'
end
# initialize hook
class GitolitePublicKeyHook < Redmine::Hook::ViewListener
render_on :view_my_account_contextual, :inline => "| <%= link_to(l(:label_public_keys), public_keys_path) %>"
end
# initialize association from user -> public keys
User.send(:has_many, :gitolite_public_keys, :dependent => :destroy)
# initialize observer
ActiveRecord::Base.observers = ActiveRecord::Base.observers << GitoliteObserver
require 'lockfile'
require 'net/ssh'
require 'tmpdir'
require 'gitolite_conf.rb'
module Gitolite
def self.repository_name project
parent_name = project.parent ? repository_name(project.parent) : ""
return "#{parent_name}/#{project.identifier}".sub(/^\//, "")
end
def self.get_urls(project)
urls = {:read_only => [], :developer => []}
read_only_baseurls = Setting.plugin_redmine_gitolite['readOnlyBaseUrls'].split(/[\r\n\t ,;]+/)
developer_baseurls = Setting.plugin_redmine_gitolite['developerBaseUrls'].split(/[\r\n\t ,;]+/)
project_path = repository_name(project) + ".git"
read_only_baseurls.each {|baseurl| urls[:read_only] << baseurl + project_path}
developer_baseurls.each {|baseurl| urls[:developer] << baseurl + project_path}
return urls
end
def self.update_repositories(projects)
projects = (projects.is_a?(Array) ? projects : [projects])
if(defined?(@recursionCheck))
if(@recursionCheck)
return
end
end
@recursionCheck = true
# Don't bother doing anything if none of the projects we've been handed have a Git repository
unless projects.detect{|p| p.repository.is_a?(Repository::Git) }.nil?
lockfile=File.new(File.join(RAILS_ROOT,"tmp",'redmine_gitolite_lock'),File::CREAT|File::RDONLY)
retries=5
loop do
break if lockfile.flock(File::LOCK_EX|File::LOCK_NB)
retries-=1
sleep 2
raise Lockfile::MaxTriesLockError if retries<=0
end
# HANDLE GIT
# create tmp dir
local_dir = File.join(RAILS_ROOT, "tmp","redmine_gitolite_#{Time.now.to_i}")
%x[mkdir "#{local_dir}"]
# Create GIT_SSH script
ssh_with_identity_file = File.join(local_dir, 'ssh_with_identity_file.sh')
File.open(ssh_with_identity_file, "w") do |f|
f.puts "#!/bin/bash"
f.puts "exec ssh -o stricthostkeychecking=no -i #{Setting.plugin_redmine_gitolite['gitoliteIdentityFile']} \"$@\""
end
File.chmod(0755, ssh_with_identity_file)
# clone repo
%x[env GIT_SSH=#{ssh_with_identity_file} git clone #{Setting.plugin_redmine_gitolite['gitoliteUrl']} #{local_dir}/gitolite]
conf = Config.new(File.join(local_dir, 'gitolite', 'conf', 'gitolite.conf'))
changed = false
projects.select{|p| p.repository.is_a?(Repository::Git)}.each do |project|
# fetch users
users = project.member_principals.map(&:user).compact.uniq
write_users = users.select{ |user| user.allowed_to?( :commit_access, project ) }
read_users = users.select{ |user| user.allowed_to?( :view_changesets, project ) && !user.allowed_to?( :commit_access, project ) }
# write key files
users.map{|u| u.gitolite_public_keys.active}.flatten.compact.uniq.each do |key|
filename = File.join(local_dir, 'gitolite/keydir',"#{key.identifier}.pub")
unless File.exists? filename
File.open(filename, 'w') {|f| f.write(key.key.gsub(/\n/,'')) }
changed = true
end
end
# delete inactives
users.map{|u| u.gitolite_public_keys.inactive}.flatten.compact.uniq.each do |key|
filename = File.join(local_dir, 'gitolite/keydir',"#{key.identifier}.pub")
if File.exists? filename
File.unlink() rescue nil
changed = true
end
end
# write config file
repo_name = repository_name(project)
read_users = read_users.map{|u| u.login.underscore}
write_users = write_users.map{|u| u.login.underscore}
conf.set_read_user repo_name, read_users
conf.set_write_user repo_name, write_users
# TODO: gitweb and git daemon support!
end
if conf.changed?
conf.save
changed = true
end
if changed
git_push_file = File.join(local_dir, 'git_push.sh')
new_dir= File.join(local_dir,'gitolite')
File.open(git_push_file, "w") do |f|
f.puts "#!/bin/sh"
f.puts "cd #{new_dir}"
f.puts "git add keydir/*"
f.puts "git add conf/gitolite.conf"
f.puts "git config user.email '#{Setting.mail_from}'"
f.puts "git config user.name 'Redmine'"
f.puts "git commit -a -m 'updated by Redmine Gitolite'"
f.puts "GIT_SSH=#{ssh_with_identity_file} git push"
end
File.chmod(0755, git_push_file)
# add, commit, push, and remove local tmp dir
%x[sh #{git_push_file}]
end
# remove local copy
%x[rm -Rf #{local_dir}]
lockfile.flock(File::LOCK_UN)
end
@recursionCheck = false
end
end
require_dependency 'redmine/scm/adapters/git_adapter'
module Gitolite
module Patches
module GitAdapterPatch
def self.included(base)
base.class_eval do
unloadable
end
base.send(:alias_method_chain, :lastrev, :time_fixed)
base.send(:alias_method_chain, :revisions, :time_fixed)
end
GIT_BIN = "git"
def lastrev_with_time_fixed(path,rev)
return nil if path.nil?
cmd = "#{GIT_BIN} --git-dir #{target('')} log --pretty=fuller --date=rfc --no-merges -n 1 "
cmd << " #{shell_quote rev} " if rev
cmd << "-- #{path} " unless path.empty?
shellout(cmd) do |io|
begin
id = io.gets.split[1]
author = io.gets.match('Author:\s+(.*)$')[1]
2.times { io.gets }
time = io.gets.match('CommitDate:\s+(.*)$')[1]
Redmine::Scm::Adapters::Revision.new({
:identifier => id,
:scmid => id,
:author => author,
:time => Time.rfc2822(time),
:message => nil,
:paths => nil
})
rescue NoMethodError => e
logger.error("The revision '#{path}' has a wrong format")
return nil
end
end
end
def revisions_with_time_fixed(path, identifier_from, identifier_to, options={})
revisions = Redmine::Scm::Adapters::Revisions.new
cmd = "#{GIT_BIN} --git-dir #{target('')} log --raw --date=rfc --pretty=fuller"
cmd << " --reverse" if options[:reverse]
cmd << " --all" if options[:all]
cmd << " -n #{options[:limit]} " if options[:limit]
cmd << " #{shell_quote(identifier_from + '..')} " if identifier_from
cmd << " #{shell_quote identifier_to} " if identifier_to
cmd << " --since=#{shell_quote(options[:since].strftime("%Y-%m-%d %H:%M:%S"))}" if options[:since]
cmd << " -- #{path}" if path && !path.empty?
shellout(cmd) do |io|
files=[]
changeset = {}
parsing_descr = 0 #0: not parsing desc or files, 1: parsing desc, 2: parsing files
revno = 1
io.each_line do |line|
if line =~ /^commit ([0-9a-f]{40})$/
key = "commit"
value = $1
if (parsing_descr == 1 || parsing_descr == 2)
parsing_descr = 0
revision = Redmine::Scm::Adapters::Revision.new({
:identifier => changeset[:commit],
:scmid => changeset[:commit],
:author => changeset[:author],
#:time => Time.parse(changeset[:date]),
:time => Time.rfc2822(changeset[:date]),
:message => changeset[:description],
:paths => files
})
if block_given?
yield revision
else
revisions << revision
end
changeset = {}
files = []
revno = revno + 1
end
changeset[:commit] = $1
elsif (parsing_descr == 0) && line =~ /^(\w+):\s*(.*)$/
key = $1
value = $2
if key == "Author"
changeset[:author] = value
elsif key == "CommitDate"
changeset[:date] = value
end
elsif (parsing_descr == 0) && line.chomp.to_s == ""
parsing_descr = 1
changeset[:description] = ""
elsif (parsing_descr == 1 || parsing_descr == 2) \
&& line =~ /^:\d+\s+\d+\s+[0-9a-f.]+\s+[0-9a-f.]+\s+(\w)\s+(.+)$/
parsing_descr = 2
fileaction = $1
filepath = $2
files << {:action => fileaction, :path => filepath}
elsif (parsing_descr == 1 || parsing_descr == 2) \
&& line =~ /^:\d+\s+\d+\s+[0-9a-f.]+\s+[0-9a-f.]+\s+(\w)\d+\s+(\S+)\s+(.+)$/
parsing_descr = 2
fileaction = $1
filepath = $3
files << {:action => fileaction, :path => filepath}
elsif (parsing_descr == 1) && line.chomp.to_s == ""
parsing_descr = 2
elsif (parsing_descr == 1)
changeset[:description] << line[4..-1]
end
end
if changeset[:commit]
revision = Redmine::Scm::Adapters::Revision.new({
:identifier => changeset[:commit],
:scmid => changeset[:commit],
:author => changeset[:author],
:time => Time.rfc2822(changeset[:date]),
:message => changeset[:description],
:paths => files
})
if block_given?
yield revision
else
revisions << revision
end
end
end
return nil if $? && $?.exitstatus != 0
revisions
end
end
end
end
Redmine::Scm::Adapters::GitAdapter.send(:include, Gitolite::Patches::GitAdapterPatch) unless Redmine::Scm::Adapters::GitAdapter.include?(Gitolite::Patches::GitAdapterPatch)
require_dependency 'repositories_controller'
module Gitolite
module Patches
module RepositoriesControllerPatch
def show_with_git_instructions
if @repository.is_a?(Repository::Git) and @repository.entries(@path, @rev).blank?
render :action => 'git_instructions'
else
show_without_git_instructions
end
end
def edit_with_scm_settings
params[:repository] ||= {}
if(@project.parent)
params[:repository][:url] = File.join(Setting.plugin_redmine_gitolite['basePath'],@project.parent.identifier,"#{@project.identifier}.git") if params[:repository_scm] == 'Git'
else
params[:repository][:url] = File.join(Setting.plugin_redmine_gitolite['basePath'],"#{@project.identifier}.git") if params[:repository_scm] == 'Git'
end
edit_without_scm_settings
end
def self.included(base)
base.class_eval do
unloadable
end
base.send(:alias_method_chain, :show, :git_instructions)
base.send(:alias_method_chain, :edit, :scm_settings)
end
end
end
end
RepositoriesController.send(:include, Gitolite::Patches::RepositoriesControllerPatch) unless RepositoriesController.include?(Gitolite::Patches::RepositoriesControllerPatch)
require_dependency 'repositories_helper'
module Gitolite
module Patches
module RepositoriesHelperPatch
def git_field_tags_with_disabled_configuration(form, repository) ; '' ; end
def self.included(base)
base.class_eval do
unloadable
end
base.send(:alias_method_chain, :git_field_tags, :disabled_configuration)
end
end
end
end
RepositoriesHelper.send(:include, Gitolite::Patches::RepositoriesHelperPatch) unless RepositoriesHelper.include?(Gitolite::Patches::RepositoriesHelperPatch)
\ No newline at end of file
module Gitolite
class Config
def initialize file_path
@path = file_path
load
end
def save
File.open(@path, "w") do |f|
f.puts content
end
@original_content = content
end
def add_write_user repo_name, users
repository(repo_name).add "RW+", users
end
def set_write_user repo_name, users
repository(repo_name).set "RW+", users
end
def add_read_user repo_name, users
repository(repo_name).add "R", users
end
def set_read_user repo_name, users
repository(repo_name).set "R", users
end
def changed?
@original_content != content
end
private
def load
@original_content = []
@repositories = {}
cur_repo_name = nil
File.open(@path).each_line do |line|
@original_content << line
tokens = line.strip.split
if tokens.first == 'repo'
cur_repo_name = tokens.last
@repositories[cur_repo_name] = AccessRights.new
next
end
cur_repo_right = @repositories[cur_repo_name]
if cur_repo_right and tokens[1] == '='
cur_repo_right.add tokens.first, tokens[2..-1]
end
end
@original_content = @original_content.join
end
def repository repo_name
@repositories[repo_name] ||= AccessRights.new
end
def content
content = []
@repositories.each do |repo, rights|
content << "repo\t#{repo}"
rights.each do |perm, users|
content << "\t#{perm}\t=\t#{users.join(' ')}" if users.length > 0
end
content << ""
end
return content.join("\n")
end
end
class AccessRights
def initialize
@rights = {}
end
def add perm, users
@rights[perm.to_sym] ||= []
@rights[perm.to_sym] << users
@rights[perm.to_sym].flatten!
@rights[perm.to_sym].uniq!
end
def set perm, users
@rights[perm.to_sym] = []
add perm, users
end
def each
@rights.each {|k,v| yield k, v}
end
end
end
namespace :gitolite do
desc "update gitolite repositories"
task :update_repositories => [:environment] do
projects = Project.active
puts "Updating repositories for projects #{projects.join(' ')}"
Gitolite.update_repositories(projects)
end
desc "fetch commits from gitolite repositories"
task :fetch_changes => [:environment] do
Repository.fetch_changesets
end
end
\ No newline at end of file
# Read about fixtures at http://ar.rubyonrails.org/classes/Fixtures.html
one:
id: 1
title: MyString
key: MyText
two:
id: 2
title: MyString
key: MyText
# Load the normal Rails helper
require File.expand_path(File.dirname(__FILE__) + '/../../../../test/test_helper')
# Ensure that we are using the temporary fixture path
Engines::Testing.set_fixture_path
require File.dirname(__FILE__) + '/../test_helper'
class GitolitePublicKeyTest < ActiveSupport::TestCase
fixtures :gitolite_public_keys
# Replace this with your real tests.
def test_truth
assert true
end
end
= Redmine Checkout plugin
Author:: Holger Just
URL:: http://dev.holgerjust.de/projects/redmine-checkout
This plugin to Redmine adds a link to the actual repository to the GUI.
This plugin includes ZeroClipboard[http://code.google.com/p/zeroclipboard/]
by Joseph Huckaby. This software is licensed under the
{GNU Lesser General Public License}[http://www.gnu.org/licenses/lgpl.html].
Copyright (c) 2009, 2010 Holger Just
Permission is hereby granted, free of charge, to any person
obtaining a copy of this software and associated documentation
files (the "Software"), to deal in the Software without
restriction, including without limitation the rights to use,
copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following
conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.
= Installation
The installation follows the standard installation path from
http://www.redmine.org/projects/redmine/wiki/Plugins
1. Copy the software to the vendor/plugins directory. Make sure that the name
of the directory is redmine_checkout.
2. Run rake db:migrate_plugins RAILS_ENV=production
3. Restart Redmine
#!/usr/bin/env ruby
require 'redmine_plugin_support'
Dir[File.expand_path(File.dirname(__FILE__)) + "/lib/tasks/**/*.rake"].sort.each { |ext| load ext }
RedminePluginSupport::Base.setup do |plugin|
plugin.project_name = 'redmine_checkout'
plugin.default_task = [:spec]
plugin.tasks = [:doc, :release, :clean, :spec, :stats]
end
\ No newline at end of file
<p><%= form.select(:checkout_overwrite, [
[l(:general_text_Yes), "1"],
[l(:general_text_No), "0"]
],
{},
:onchange => <<-EOF
Effect.toggle($('checkout_settings'), 'slide', {duration:0.2});
EOF
)%></p>
<div id="checkout_settings" <%= 'style="display:none;"' unless form.object.checkout_overwrite? %>><fieldset>
<legend><%=l :label_checkout %></legend>
<p><%= form.text_area :checkout_description, :cols => 60, :rows => 5, :class => 'wiki-edit', :label => :field_description %></p>
<%= wikitoolbar_for 'repository_checkout_description' %>
<% if form.object.scm_name == 'Subversion' %>
<p><%= form.select :checkout_display_login,[
[l(:label_display_login_none), ''],
[l(:label_display_login_username), 'username'],
[l(:label_display_login_password), 'password']
],
:label => :setting_checkout_display_login %></p>
<% end %>
<p><%= form.check_box :checkout_display_command %></p>
<% javascript_tag do %>
protocolForm = new Subform(
'<%= escape_javascript(render(:partial => "projects/settings/repository_checkout_protocol", :locals => {:protocol => Checkout::Protocol.new({:protocol => form.object.scm_name, :append_path => form.object.allow_subtree_checkout? ? 1: 0, :repository => form.object})})) %>',
<%= form.object.checkout_protocols.length %>,
'checkout_protocol_table'
);
<% end %>
<p><label><%=l :label_protocol_plural %></label><%=l :help_repository_checkout_protocols %></p>
<%= hidden_field_tag 'repository[checkout_protocols][-1][protocol]', 'empty' %>
<table class="list checkout_protocol_table">
<thead><tr>
<th class="protocol_protocol" ><%= l(:setting_protocol)%></th>
<th class="protocol_command" ><%= l(:setting_checkout_command)%></th>
<th class="protocol_fixed_url" ><%= l(:setting_checkout_fixed_url) %></th>
<th class="protocol_access" ><%= l(:label_permissions) %></th>
<th class="protocol_append_path"><%= l(:label_append_path) %></th>
<th class="protocol_is_default" ><%= l(:label_default) %></th>
<th class="protocol_delete" ></th>
</tr></thead>
<tbody id="checkout_protocol_table">
<% form.object.checkout_protocols.each_with_index do |protocol, index| %>
<%= render :partial => 'projects/settings/repository_checkout_protocol', :locals => {:protocol => protocol, :index => index, :classes => cycle('odd', 'even')} %>
<% end %>
</tbody>
</table>
<div style="text-align: right"><%= link_to_function l(:button_add_protocol), "protocolForm.add()", {:class => "icon icon-add"} %></div>
</fieldset></div>
<%
index ||= "--INDEX--"
classes ||= ""
protocol = Checkout::Protocol.new(protocol) unless protocol.is_a? Checkout::Protocol
%>
<tr id="<%= "checkout_protocols_#{index}" %>" class="<%= classes %>" <%= 'style="display:none"' if index == '--INDEX--' %>>
<td class="protocol_protocol"><%= text_field_tag "repository[checkout_protocols][#{index}][protocol]", protocol.protocol, :size => 10 %></td>
<td class="protocol_command"><%= text_field_tag "repository[checkout_protocols][#{index}][command]", protocol.command, :size => 15 %></td>
<td class="protocol_fixed_url"><%= text_field_tag "repository[checkout_protocols][#{index}][fixed_url]", protocol.fixed_url, :size => 60 %></td>
<td class="protocol_access"><%= select_tag "repository[checkout_protocols][#{index}][access]", options_for_select([
[l(:label_access_read_write), 'read+write'],
[l(:label_access_read_only), 'read-only'],
[l(:label_access_permission), 'permission']], protocol.access) %></td>
<td class="protocol_append_path"><%= check_box_tag "repository[checkout_protocols][#{index}][append_path]", 1, protocol.append_path? %></td>
<td class="protocol_is_default"><%= check_box_tag "repository[checkout_protocols][#{index}][is_default]", 1, protocol.default? %></td>
<td class="protocol_delete"><%= image_to_function 'delete.png', "var e=$('checkout_protocols_#{index}');var parent=e.up(\"tbody\");e.remove();recalculate_even_odd(parent);return false" %></td>
</tr>
<div class="repository-info">
<% if repository.checkout_description.present? %>
<div class="wiki<%= ' bottomline' if protocols.present? %>"><%= textilizable repository.checkout_description %></div>
<% end %>
<% if protocols.present? %>
<div id="checkout_box">
<ul id="checkout_protocols">
<% protocols.each do |p| -%>
<li>
<a <%= 'class="selected"' if p == default_protocol %> id="checkout_protocol_<%= p.protocol.to_s.underscore %>" data-permission="<%= p.access_rw(User.current) %>" href="<%= URI.escape p.url(checkout_path) %>"><%=h p.protocol %></a>
</li>
<% end -%>
</ul>
<%= text_field_tag :checkout_url, h(default_protocol.full_command(checkout_path)), :readonly => true %>
<%- if Setting.checkout_use_zero_clipboard? %>
<div id="clipboard_container" title="<%= l(:label_copy_to_clipboard) %>" style="display: none;">
<div id="clipboard_button"><%= image_tag 'paste.png', :plugin => 'redmine_checkout' %></div>
</div>
<% end -%>
<% if default_protocol %><p><%=l :label_access_type, :type => l(default_protocol.access_label(User.current)) %></p><% end %>
<% javascript_tag do %>
var checkout_access = $H({<%= protocols.inject([]){|r,p| r << "'checkout_protocol_#{p.protocol.to_s.underscore}': '#{l(p.access_label(User.current))}'"}.join(', ') %>});
var checkout_commands = $H({<%= protocols.inject([]){|r,p| r << "'checkout_protocol_#{p.protocol.to_s.underscore}': '#{escape_javascript(p.full_command(checkout_path))}'"}.join(', ') %>});
<%- if Setting.checkout_use_zero_clipboard? %>ZeroClipboard.setMoviePath( '<%= image_path('ZeroClipboard.swf', :plugin => 'redmine_checkout') %>' );<% end %>
<% end %>
</div>
<% end%>
</div>
<div style="clear: left"></div>
<% content_for :header_tags do %>
<%= stylesheet_link_tag 'checkout', :plugin => 'redmine_checkout' %>
<%= javascript_include_tag 'checkout', :plugin => 'redmine_checkout' %>
<%= (javascript_include_tag 'ZeroClipboard', :plugin => 'redmine_checkout') if Setting.checkout_use_zero_clipboard? %>
<% end %>
\ No newline at end of file
<% form_tag({:action => 'edit', :tab => 'checkout'}) do %>
<% javascript_tag do %>
protocolForms = $H();
document.observe("dom:loaded", function() {
$('tab-content-checkout').select('fieldset.collapsed').each(function(e){
e.down('div').hide();
});
<%
CheckoutHelper.supported_scm.select{|scm| Setting.enabled_scm.include?(scm)}.each do |scm|
next if Setting.send("checkout_overwrite_description_#{scm}?")
-%>
$('settings_checkout_description_<%= scm %>').up('div').up('div').hide();
<%- end %>
});
<% end %>
<div class="box tabular settings">
<p><%= setting_check_box :checkout_display_checkout_info %></p>
<p><%= setting_text_area :checkout_description_Abstract, :cols => 60, :rows => 5, :class => 'wiki-edit', :label => :field_description %></p>
<%= wikitoolbar_for 'settings_checkout_description_Abstract' %>
<p><%= setting_check_box :checkout_use_zero_clipboard %></p>
<% CheckoutHelper.supported_scm.select{|scm| Setting.enabled_scm.include?(scm)}.each do |scm| -%>
<fieldset class="collapsible collapsed">
<legend onclick="toggleFieldset(this);"><%= "Repository::#{scm}".constantize.scm_name %></legend>
<div><%= render :partial => 'checkout_scm', :locals => {:scm => scm} %></div>
</fieldset>
<%- end %>
</div>
<%= submit_tag l(:button_save) %>
<%- end %>
<% content_for :header_tags do %>
<%= javascript_include_tag 'subform', :plugin => 'redmine_checkout' %>
<%= stylesheet_link_tag 'checkout', :plugin => 'redmine_checkout' %>
<% end %>
<%
index ||= "--INDEX--"
classes ||= ""
protocol = Checkout::Protocol.new(protocol) unless protocol.is_a? Checkout::Protocol
%>
<tr id="<%= "checkout_protocols_#{scm}_#{index}" %>" class="<%= classes %>" <%= 'style="display:none"' if index == '--INDEX--' %>>
<td class="protocol_protocol" ><%= text_field_tag "settings[checkout_protocols_#{scm}][#{index}][protocol]", protocol.protocol, :size => 10 %></td>
<td class="protocol_command" ><%= text_field_tag "settings[checkout_protocols_#{scm}][#{index}][command]", protocol.command, :size => 15 %></td>
<td class="protocol_regex" ><%= text_field_tag "settings[checkout_protocols_#{scm}][#{index}][regex]", protocol.regex, :size => 30 %></td>
<td class="protocol_regex_replacement"><%= text_field_tag "settings[checkout_protocols_#{scm}][#{index}][regex_replacement]", protocol.regex_replacement, :size => 30 %></td>
<td class="protocol_access" ><%= select_tag "settings[checkout_protocols_#{scm}][#{index}][access]", options_for_select([
[l(:label_access_read_write), 'read+write'],
[l(:label_access_read_only), 'read-only'],
[l(:label_access_permission), 'permission']], protocol.access) %></td>
<td class="protocol_append_path"><%= check_box_tag "settings[checkout_protocols_#{scm}][#{index}][append_path]", 1, protocol.append_path? %></td>
<td class="protocol_is_default"><%= check_box_tag "settings[checkout_protocols_#{scm}][#{index}][is_default]", 1, protocol.default? %></td>
<td class="protocol_delete"><%= image_to_function 'delete.png', "var e=$('checkout_protocols_#{scm}_#{index}');var parent=e.up(\"tbody\");e.remove();recalculate_even_odd(parent);return false" %></td>
</tr>
<div>
<p><%= setting_check_box "checkout_overwrite_description_#{scm}", :label => :setting_checkout_overwrite_description, :onclick => <<-EOF
Effect.toggle($('settings_checkout_description_#{scm}').up("div").up("div"), 'slide', {duration:0.2});
EOF
%></p>
<div>
<p><%= setting_text_area "checkout_description_#{scm}", :cols => 60, :rows => 5, :class => 'wiki-edit', :label => :field_description %></p>
<%= wikitoolbar_for "settings_checkout_description_#{scm}" %>
</div>
<% if scm == 'Subversion' %>
<p><%= setting_select "checkout_display_login",[
[l(:label_display_login_username), 'username'],
[l(:label_display_login_password), 'password']
],
:blank => :label_display_login_none %></p>
<% end %>
<p><%= setting_check_box "checkout_display_command_#{scm}", :label => :field_checkout_display_command %></p>
<% javascript_tag do %>
<% repo = "Repository::#{scm}".constantize %>
var subform = new Subform('<%= escape_javascript(render(:partial => "checkout_protocol", :locals => {:protocol => Checkout::Protocol.new({:protocol => repo.scm_name, :append_path => (repo.allow_subtree_checkout? ? '1' : '0'), :command => repo.checkout_default_command}), :scm => scm})) %>',<%= Setting.send("checkout_protocols_#{scm}").length %>,'settings_checkout_protocols_<%= scm %>');
protocolForms.set('<%= scm %>', subform);
<% end %>
<p><label><%=l :label_protocol_plural %></label><%=l :help_checkout_protocols %></p>
<table class="list checkout_protocol_table">
<thead><tr>
<th class="protocol_protocol" ><%= l(:setting_protocol)%></th>
<th class="protocol_command" ><%= l(:setting_checkout_command)%></th>
<th class="protocol_regex" ><%= l(:setting_checkout_url_regex) %></th>
<th class="protocol_regex_replacement"><%= l(:setting_checkout_url_regex_replacement) %></th>
<th class="protocol_access" ><%= l(:label_permissions) %></th>
<th class="protocol_append_path" ><%= l(:label_append_path) %></th>
<th class="protocol_is_default" ><%= l(:label_default) %></th>
<th class="protocol_delete" ></th>
</tr></thead>
<tbody id="settings_checkout_protocols_<%= scm %>">
<% Setting.send("checkout_protocols_#{scm}").each_with_index do |protocol, index| %>
<%= render :partial => 'checkout_protocol', :locals => {:protocol => Checkout::Protocol.new(protocol), :scm => scm, :index => index, :classes => cycle('odd', 'even')} %>
<% end %>
</tbody>
</table>
<div style="text-align: right"><%= link_to_function l(:button_add_protocol), "protocolForms.get('#{scm}').add()", {:class => "icon icon-add"} %></div>
</div>
<%=l(:help_moved_settings, :link => link_to(l(:label_settings_location), {:controller => 'settings', :action => 'index', :tab => 'checkout'})) %>
package {
// Simple Set Clipboard System
// Author: Joseph Huckaby
import flash.display.Stage;
import flash.display.Sprite;
import flash.display.LoaderInfo;
import flash.display.StageScaleMode;
import flash.events.*;
import flash.display.StageAlign;
import flash.display.StageScaleMode;
import flash.external.ExternalInterface;
import flash.system.Security;
import flash.utils.*;
import flash.system.System;
public class ZeroClipboard extends Sprite {
private var id:String = '';
private var button:Sprite;
private var clipText:String = '';
public function ZeroClipboard() {
// constructor, setup event listeners and external interfaces
stage.scaleMode = StageScaleMode.EXACT_FIT;
flash.system.Security.allowDomain("*");
// import flashvars
var flashvars:Object = LoaderInfo( this.root.loaderInfo ).parameters;
id = flashvars.id;
// invisible button covers entire stage
button = new Sprite();
button.buttonMode = true;
button.useHandCursor = true;
button.graphics.beginFill(0xCCFF00);
button.graphics.drawRect(0, 0, Math.floor(flashvars.width), Math.floor(flashvars.height));
button.alpha = 0.0;
addChild(button);
button.addEventListener(MouseEvent.CLICK, clickHandler);
button.addEventListener(MouseEvent.MOUSE_OVER, function(event:Event) {
ExternalInterface.call( 'ZeroClipboard.dispatch', id, 'mouseOver', null );
} );
button.addEventListener(MouseEvent.MOUSE_OUT, function(event:Event) {
ExternalInterface.call( 'ZeroClipboard.dispatch', id, 'mouseOut', null );
} );
button.addEventListener(MouseEvent.MOUSE_DOWN, function(event:Event) {
ExternalInterface.call( 'ZeroClipboard.dispatch', id, 'mouseDown', null );
} );
button.addEventListener(MouseEvent.MOUSE_UP, function(event:Event) {
ExternalInterface.call( 'ZeroClipboard.dispatch', id, 'mouseUp', null );
} );
// external functions
ExternalInterface.addCallback("setHandCursor", setHandCursor);
ExternalInterface.addCallback("setText", setText);
// signal to the browser that we are ready
ExternalInterface.call( 'ZeroClipboard.dispatch', id, 'load', null );
}
public function setText(newText) {
// set the maximum number of files allowed
clipText = newText;
}
public function setHandCursor(enabled:Boolean) {
// control whether the hand cursor is shown on rollover (true)
// or the default arrow cursor (false)
button.useHandCursor = enabled;
}
private function clickHandler(event:Event):void {
// user click copies text to clipboard
// as of flash player 10, this MUST happen from an in-movie flash click event
System.setClipboard( clipText );
ExternalInterface.call( 'ZeroClipboard.dispatch', id, 'complete', clipText );
}
}
}
<?xml version="1.0" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg width="100%" height="100%" version="1.1" xmlns="http://www.w3.org/2000/svg">
<defs>
<linearGradient id="gradient" x1="100%" y1="100%">
<stop offset="0%" style="stop-color:#ddd; stop-opacity:1" />
<stop offset="100%" style="stop-color:#f8f8f8; stop-opacity:1" />
</linearGradient>
</defs>
<rect width="100%" height="100%" style="fill:url(#gradient)"/>
</svg>
\ No newline at end of file
<?xml version="1.0" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg width="100%" height="100%" version="1.1" xmlns="http://www.w3.org/2000/svg">
<defs>
<linearGradient id="gradient" x1="100%" y1="100%">
<stop offset="0%" style="stop-color:#507AAA; stop-opacity:1" />
<stop offset="100%" style="stop-color:#759fcf; stop-opacity:1" />
</linearGradient>
</defs>
<rect width="100%" height="100%" style="fill:url(#gradient)"/>
</svg>
\ No newline at end of file
<?xml version="1.0" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg width="100%" height="100%" version="1.1" xmlns="http://www.w3.org/2000/svg">
<defs>
<linearGradient id="gradient" x1="100%" y1="100%">
<stop offset="0%" style="stop-color:#aaa; stop-opacity:1" />
<stop offset="100%" style="stop-color:#ccc; stop-opacity:1" />
</linearGradient>
</defs>
<rect width="100%" height="100%" style="fill:url(#gradient)"/>
</svg>
\ No newline at end of file
// Simple Set Clipboard System
// Author: Joseph Huckaby
var ZeroClipboard = {
version: "1.0.7",
clients: {}, // registered upload clients on page, indexed by id
moviePath: 'ZeroClipboard.swf', // URL to movie
nextId: 1, // ID of next movie
$: function(thingy) {
// simple DOM lookup utility function
if (typeof(thingy) == 'string') thingy = document.getElementById(thingy);
if (!thingy.addClass) {
// extend element with a few useful methods
thingy.hide = function() { this.style.display = 'none'; };
thingy.show = function() { this.style.display = ''; };
thingy.addClass = function(name) { this.removeClass(name); this.className += ' ' + name; };
thingy.removeClass = function(name) {
var classes = this.className.split(/\s+/);
var idx = -1;
for (var k = 0; k < classes.length; k++) {
if (classes[k] == name) { idx = k; k = classes.length; }
}
if (idx > -1) {
classes.splice( idx, 1 );
this.className = classes.join(' ');
}
return this;
};
thingy.hasClass = function(name) {
return !!this.className.match( new RegExp("\\s*" + name + "\\s*") );
};
}
return thingy;
},
setMoviePath: function(path) {
// set path to ZeroClipboard.swf
this.moviePath = path;
},
dispatch: function(id, eventName, args) {
// receive event from flash movie, send to client
var client = this.clients[id];
if (client) {
client.receiveEvent(eventName, args);
}
},
register: function(id, client) {
// register new client to receive events
this.clients[id] = client;
},
getDOMObjectPosition: function(obj, stopObj) {
// get absolute coordinates for dom element
var info = {
left: 0,
top: 0,
width: obj.width ? obj.width : obj.offsetWidth,
height: obj.height ? obj.height : obj.offsetHeight
};
while (obj && (obj != stopObj)) {
info.left += obj.offsetLeft;
info.top += obj.offsetTop;
obj = obj.offsetParent;
}
return info;
},
Client: function(elem) {
// constructor for new simple upload client
this.handlers = {};
// unique ID
this.id = ZeroClipboard.nextId++;
this.movieId = 'ZeroClipboardMovie_' + this.id;
// register client with singleton to receive flash events
ZeroClipboard.register(this.id, this);
// create movie
if (elem) this.glue(elem);
}
};
ZeroClipboard.Client.prototype = {
id: 0, // unique ID for us
ready: false, // whether movie is ready to receive events or not
movie: null, // reference to movie object
clipText: '', // text to copy to clipboard
handCursorEnabled: true, // whether to show hand cursor, or default pointer cursor
cssEffects: true, // enable CSS mouse effects on dom container
handlers: null, // user event handlers
glue: function(elem, appendElem, stylesToAdd) {
// glue to DOM element
// elem can be ID or actual DOM element object
this.domElement = ZeroClipboard.$(elem);
// float just above object, or zIndex 99 if dom element isn't set
var zIndex = 99;
if (this.domElement.style.zIndex) {
zIndex = parseInt(this.domElement.style.zIndex, 10) + 1;
}
if (typeof(appendElem) == 'string') {
appendElem = ZeroClipboard.$(appendElem);
}
else if (typeof(appendElem) == 'undefined') {
appendElem = document.getElementsByTagName('body')[0];
}
// find X/Y position of domElement
var box = ZeroClipboard.getDOMObjectPosition(this.domElement, appendElem);
// create floating DIV above element
this.div = document.createElement('div');
var style = this.div.style;
style.position = 'absolute';
style.left = '' + box.left + 'px';
style.top = '' + box.top + 'px';
style.width = '' + box.width + 'px';
style.height = '' + box.height + 'px';
style.zIndex = zIndex;
if (typeof(stylesToAdd) == 'object') {
for (addedStyle in stylesToAdd) {
style[addedStyle] = stylesToAdd[addedStyle];
}
}
// style.backgroundColor = '#f00'; // debug
appendElem.appendChild(this.div);
this.div.innerHTML = this.getHTML( box.width, box.height );
},
getHTML: function(width, height) {
// return HTML for movie
var html = '';
var flashvars = 'id=' + this.id +
'&width=' + width +
'&height=' + height;
if (navigator.userAgent.match(/MSIE/)) {
// IE gets an OBJECT tag
var protocol = location.href.match(/^https/i) ? 'https://' : 'http://';
html += '<object classid="clsid:d27cdb6e-ae6d-11cf-96b8-444553540000" codebase="'+protocol+'download.macromedia.com/pub/shockwave/cabs/flash/swflash.cab#version=9,0,0,0" width="'+width+'" height="'+height+'" id="'+this.movieId+'" align="middle"><param name="allowScriptAccess" value="always" /><param name="allowFullScreen" value="false" /><param name="movie" value="'+ZeroClipboard.moviePath+'" /><param name="loop" value="false" /><param name="menu" value="false" /><param name="quality" value="best" /><param name="bgcolor" value="#ffffff" /><param name="flashvars" value="'+flashvars+'"/><param name="wmode" value="transparent"/></object>';
}
else {
// all other browsers get an EMBED tag
html += '<embed id="'+this.movieId+'" src="'+ZeroClipboard.moviePath+'" loop="false" menu="false" quality="best" bgcolor="#ffffff" width="'+width+'" height="'+height+'" name="'+this.movieId+'" align="middle" allowScriptAccess="always" allowFullScreen="false" type="application/x-shockwave-flash" pluginspage="http://www.macromedia.com/go/getflashplayer" flashvars="'+flashvars+'" wmode="transparent" />';
}
return html;
},
hide: function() {
// temporarily hide floater offscreen
if (this.div) {
this.div.style.left = '-2000px';
}
},
show: function() {
// show ourselves after a call to hide()
this.reposition();
},
destroy: function() {
// destroy control and floater
if (this.domElement && this.div) {
this.hide();
this.div.innerHTML = '';
var body = document.getElementsByTagName('body')[0];
try { body.removeChild( this.div ); } catch(e) {;}
this.domElement = null;
this.div = null;
}
},
reposition: function(elem) {
// reposition our floating div, optionally to new container
// warning: container CANNOT change size, only position
if (elem) {
this.domElement = ZeroClipboard.$(elem);
if (!this.domElement) this.hide();
}
if (this.domElement && this.div) {
var box = ZeroClipboard.getDOMObjectPosition(this.domElement);
var style = this.div.style;
style.left = '' + box.left + 'px';
style.top = '' + box.top + 'px';
}
},
setText: function(newText) {
// set text to be copied to clipboard
this.clipText = newText;
if (this.ready) this.movie.setText(newText);
},
addEventListener: function(eventName, func) {
// add user event listener for event
// event types: load, queueStart, fileStart, fileComplete, queueComplete, progress, error, cancel
eventName = eventName.toString().toLowerCase().replace(/^on/, '');
if (!this.handlers[eventName]) this.handlers[eventName] = [];
this.handlers[eventName].push(func);
},
setHandCursor: function(enabled) {
// enable hand cursor (true), or default arrow cursor (false)
this.handCursorEnabled = enabled;
if (this.ready) this.movie.setHandCursor(enabled);
},
setCSSEffects: function(enabled) {
// enable or disable CSS effects on DOM container
this.cssEffects = !!enabled;
},
receiveEvent: function(eventName, args) {
// receive event from flash
eventName = eventName.toString().toLowerCase().replace(/^on/, '');
// special behavior for certain events
switch (eventName) {
case 'load':
// movie claims it is ready, but in IE this isn't always the case...
// bug fix: Cannot extend EMBED DOM elements in Firefox, must use traditional function
this.movie = document.getElementById(this.movieId);
if (!this.movie) {
var self = this;
setTimeout( function() { self.receiveEvent('load', null); }, 1 );
return;
}
// firefox on pc needs a "kick" in order to set these in certain cases
if (!this.ready && navigator.userAgent.match(/Firefox/) && navigator.userAgent.match(/Windows/)) {
var self = this;
setTimeout( function() { self.receiveEvent('load', null); }, 100 );
this.ready = true;
return;
}
this.ready = true;
this.movie.setText( this.clipText );
this.movie.setHandCursor( this.handCursorEnabled );
break;
case 'mouseover':
if (this.domElement && this.cssEffects) {
this.domElement.addClass('hover');
if (this.recoverActive) this.domElement.addClass('active');
}
break;
case 'mouseout':
if (this.domElement && this.cssEffects) {
this.recoverActive = false;
if (this.domElement.hasClass('active')) {
this.domElement.removeClass('active');
this.recoverActive = true;
}
this.domElement.removeClass('hover');
}
break;
case 'mousedown':
if (this.domElement && this.cssEffects) {
this.domElement.addClass('active');
}
break;
case 'mouseup':
if (this.domElement && this.cssEffects) {
this.domElement.removeClass('active');
this.recoverActive = false;
}
break;
} // switch eventName
if (this.handlers[eventName]) {
for (var idx = 0, len = this.handlers[eventName].length; idx < len; idx++) {
var func = this.handlers[eventName][idx];
if (typeof(func) == 'function') {
// actual function reference
func(this, args);
}
else if ((typeof(func) == 'object') && (func.length == 2)) {
// PHP style object + method, i.e. [myObject, 'myMethod']
func[0][ func[1] ](this, args);
}
else if (typeof(func) == 'string') {
// name of function
window[func](this, args);
}
} // foreach event handler defined
} // user defined handler for event
}
};
document.observe("dom:loaded", function() {
/* update the checkout URL if clicked on a protocol */
$('checkout_protocols').select('a').each(function(e) {
e.observe('click', function(event) {
$('checkout_url').value = checkout_commands.get(this.id);
$('checkout_protocols').select('a').each(function(e) {
e.removeClassName("selected");
});
this.addClassName("selected")
var value = checkout_access.get(this.id);
$('checkout_access').innerHTML = value;
event.stop();
});
});
/* select the text field contents if activated */
Event.observe('checkout_url', 'click', function(event) {
this.activate();
});
if (typeof('ZeroClipboard') != 'undefined') {
$('clipboard_container').show();
clipboard = new ZeroClipboard.Client();
clipboard.setHandCursor( true );
clipboard.glue('clipboard_button', 'clipboard_container');
clipboard.addEventListener('mouseOver', function (client) {
clipboard.setText( $('checkout_url').value );
});
}
});
var Subform = Class.create({
lineIndex: 1,
parentElement: "",
initialize: function(rawHTML, lineIndex, parentElement) {
this.rawHTML = rawHTML;
this.lineIndex = lineIndex;
this.parentElement = parentElement;
},
parsedHTML: function() {
return this.rawHTML.replace(/--INDEX--/g, this.lineIndex++);
},
add: function() {
var e = $(this.parentElement);
Element.insert(e, { bottom: this.parsedHTML()});
Effect.toggle(e.childElements().last(), 'slide', {duration:0.2});
recalculate_even_odd(e);
},
add_after: function(e) {
Element.insert(e, { after: this.parsedHTML()});
Effect.toggle(e.next(), 'slide', {duration:0.2});
recalculate_even_odd($(this.parentElement));
},
add_on_top: function() {
var e = $(this.parentElement);
Element.insert(e, { top: this.parsedHTML()});
Effect.toggle(e.childElements().first(), 'slide', {duration:0.2});
recalculate_even_odd(e);
}
});
function recalculate_even_odd(element) {
$A(element.childElements()).inject(
0,
function(acc, e)
{
e.removeClassName("even");
e.removeClassName("odd");
e.addClassName( (acc%2==0) ? "odd" : "even"); return ++acc;
}
)
}
/* Uncomment the following line for nicer tables if you use the alternate theme (or derived). */
/* @import url(checkout_alternate.css); */
table.checkout_protocol_table td { padding-right: 6px; vertical-align: middle; /* Double the border with of text input boxes */ }
table.checkout_protocol_table td.protocol_access { padding-right: 0; }
table.checkout_protocol_table td input[type=text], .checkout_protocol_table td select { width: 100%; }
table.checkout_protocol_table td.protocol_delete { width: 16px; }
table.checkout_protocol_table td.protocol_append_path, table.checkout_protocol_table td.protocol_is_default { text-align: center; }
.icon-changeset { background-image: url(../../../images/changeset.png);}
.repository-info {
background-color: #eee;
border: 1px solid #E4E4E4;
padding: 0 10px;
margin: 4px 0 10px;
}
.bottomline {
border-bottom: 1px solid #ccc;
}
#checkout_box {
margin: 10px 0;
}
#checkout_protocols {
height: 23px;
float: left;
margin: 0;
padding: 0;
}
#checkout_protocols li {
float: left;
list-style-type: none;
margin: 0;
padding: 0;
}
#checkout_protocols li:first-child a {
border-left-width: 1px;
/* Standard, Opera 10, IE 9 */
border-top-left-radius: 3px;
border-bottom-left-radius: 3px;
/* Konquerer */
-khtml-border-top-left-radius: 3px;
-khtml-border-bottom-left-radius: 3px;
/* Gecko (Firefox, ...) */
-moz-border-radius: 3px 0 0 3px;
/* Webkit (Chrome, Safari, ...) */
-webkit-border-top-left-radius: 3px;
-webkit-border-bottom-left-radius: 3px;
/* IE <= 9 not supported */
}
#checkout_protocols li a,
#clipboard_button {
background-color: #eee;
background: url(../images/button.svg) 0 0 no-repeat; /* Opera needs an "image" :( - using svg for this so it will scale properly without looking too ugly */
background: -khtml-gradient(linear, left top, left bottom, from(#f8f8f8), to(#ddd)); /* Konquerer */
background: -moz-linear-gradient(top, #f8f8f8, #ddd); /* Gecko (Firefox, ...) */
background: -webkit-gradient(linear, left top, left bottom, from(#f8f8f8), to(#ddd)); /* Webkit (Chrome, Safari, ...) */
filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#f8f8f8', endColorstr='#dddddd'); /* IE 5.5 - 7 */
-ms-filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#f8f8f8', endColorstr='#dddddd'); /* IE 8 */
border-color: #bbb;
border-style: solid;
border-width: 1px 1px 1px 0;
color: #333;
display: block;
font-size: 11px;
font-weight: bold;
line-height: 21px;
margin: 0;
padding: 0 10px 0 11px;
text-decoration: none;
text-shadow: 1px 1px 0 #fff;
position: relative; /* to please IE */
}
#checkout_protocols li a:hover, #checkout_protocols li a.selected:hover {
background-color: #507AAA;
background: url(../images/button_focus.svg) 0 0 no-repeat; /* Opera needs an "image" :( - using svg for this so it will scale properly without looking too ugly */
background: -khtml-gradient(linear, left top, left bottom, from(#759fcf), to(#507AAA)); /* Konquerer */
background: -moz-linear-gradient(top, #759fcf, #507AAA); /* Gecko (Firefox, ...) */
background: -webkit-gradient(linear, left top, left bottom, from(#759fcf), to(#507AAA)); /* Webkit (Chrome, Safari, ...) */
filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#759fcf', endColorstr='#507AAA'); /* IE 5.5 - IE 7 */
-ms-filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#759fcf', endColorstr='#507AAA'); /* IE 8 */
color: #fff;
text-shadow: -1px -1px 0 rgba(0,0,0,0.4);
border-top-color: #759fcf;
border-bottom-color: #507AAA;
text-decoration: none;
}
#checkout_protocols li a.selected,
#clipboard_button.active {
background-color: #bbb;
background: url(../images/button_selected.svg) 0 0 no-repeat; /* Opera needs an "image" :( - using svg for this so it will scale properly without looking too ugly */
background: -webkit-gradient(linear, left top, left bottom, from(#ccc), to(#aaa)); /* Konquerer */
background: -moz-linear-gradient(top, #ccc, #aaa); /* Gecko (Firefox, ...) */
background: -webkit-gradient(linear, left top, left bottom, from(#ccc), to(#aaa)); /* Webkit (Chrome, Safari, ...) */
filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#cccccc', endColorstr='#aaaaaa'); /* IE 5.5 - IE 7 */
-ms-filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#cccccc', endColorstr='#aaaaaa'); /* IE 8 */
color: #000;
text-shadow: 1px 1px 0 rgba(255,255,255,0.4);
border-color: #bbb;
}
#checkout_url {
border: 1px solid #bbb;
border-width: 1px 1px 1px 0;
background-color: #fff;
color: #000;
font-size: 11px;
height: 16px;
padding: 3px 5px 2px;
width: 400px;
font-family: Monaco,"DejaVu Sans Mono","Bitstream Vera Sans Mono","Courier New",monospace;
margin: 0 5px 0 0;
float: left;
}
#checkout_box p {
color: #666;
line-height: 23px;
font-size: 11px;
margin: 0 0 0 5px;
}
span#checkout_access {
font-weight: bold;
}
#clipboard_container {
position: relative;
float: left;
margin-right: 5px;
}
#clipboard_button {
height: 21px;
width: 23px;
padding: 0;
border-width: 1px;
text-align: center;
border-radius: 5px; /* Standard, Opera 10, IE 9 */
-khtml-border-radius: 3px; /* Konquerer */
-moz-border-radius: 3px ; /* Gecko (Firefox, ...) */
-webkit-border-radius: 3px; /* Webkit (Chrome, Safari, ...) */
/* IE <= 9 not supported */
}
#clipboard_button img {
padding-top: 2px;
}
table.checkout_protocol_table td { padding-right: 10px !important; /* Double the border with of text input boxes */ }
table.checkout_protocol_table td.protocol_access { padding-right: 2px !important; }
ca:
label_checkout: "Checkout"
setting_checkout_display_checkout_info: "Mostra la informació d'accés al dipòsit"
setting_checkout_fixed_url: "Adreça del dipòsit"
setting_checkout_url_regex: "Expressió regular"
setting_checkout_url_regex_replacement: "Text de substitució"
setting_checkout_display_login: "Mostra el nom d'usuari"
setting_checkout_command: "Ordre d'obtenció"
setting_checkout_use_zero_clipboard: 'Mostra el botó "Copia al porta-retalls"'
setting_checkout_overwrite_description: "Sobreescriu la descripció per defecte"
field_checkout_overwrite: "Sobreescriu els paràmetres per defecte pels protocols d'obtenció"
field_checkout_display_command: "Mostra l'ordre d'obtenció"
label_protocol_plural: "Protocols"
button_add_protocol: "Afegir un protocol"
label_access_type: "L'accés a aquesta adreça <span id='checkout_access'>{{type}}</span>."
label_access_read_only: "és només de lectura"
label_access_read_write: "és de lectura i escriptura"
label_access_permission: "depèn dels permisos d'usuaris"
label_append_path: "Afegir una ruta"
label_display_login_none: "No mostris el nom d'usuari o la contrasenya"
label_display_login_username: "Mostra el nom d'usuari però no la contrasenya"
label_display_login_password: "Mostra el nom d'usuari i la contrasenya"
label_copy_to_clipboard: "Copia al porta-retalls"
help_checkout_protocols: |
The URLs in protocols are generated from applying the regular expression
and the replacement text to the original URL. The replacement text
supports back-references to braced expressions using the \1 notation.
help_repository_checkout_protocols: |
Deixa el camp "Adreça del dipòsit" en blanc per fer servir l'adreça del dipòsit definida.
help_moved_settings: "S'ha desplaçat la pàgina de configuració cap a {{link}}."
label_settings_location: "Administració -> Paràmetres -> Checkout"
de:
label_checkout: "Checkout"
setting_checkout_display_checkout_info: "Checkout-Informationen anzeigen"
setting_checkout_fixed_url: "Checkout-URL"
setting_checkout_url_regex: "Regulärer Ausdruck"
setting_checkout_url_regex_replacement: "Ersatztext"
setting_checkout_display_login: "Mitgliedsnamen anzeigen"
setting_checkout_command: "Checkout-Befehl"
setting_checkout_use_zero_clipboard: "Zwischenablagen-Helfer anzeigen"
setting_checkout_overwrite_description: "Standard-Beschreibung überschreiben"
field_checkout_overwrite: "Überschreibe Standardeinstellung für Checkout-Protokolle"
field_checkout_display_command: "Checkout-Befehl anzeigen"
label_protocol_plural: "Protokolle"
button_add_protocol: "Protokoll hinzufügen"
label_access_type: 'Diese URL erlaubt Zugriff zum <span id="checkout_access">{{type}}</span>.'
label_access_read_only: 'Nur-Lesen'
label_access_read_write: "Lesen+Schreiben"
label_access_permission: "Abhängig von Benutzer-Rechten"
label_append_path: "Pfad anhängen"
label_display_login_none: "Mitgliedsnamen nicht anzeigen"
label_display_login_username: "Mitgliedsnamen anzeigen, aber kein Kennwort"
label_display_login_password: "Mitgliedsnamen und Kennwort anzeigen"
label_copy_to_clipboard: "In die Zwischenablage kopieren"
help_checkout_protocols: |
Die URLs in Protokollen werden aus der originalen URL erzeugt, auf die der
reguläre Ausdruck und der Ersatztext angewendet werden. Der Ersatztext
erlaubt Rückwärtsreferenzen zu geklammerten Audrücken mit der \1 Notation.
help_repository_checkout_protocols: |
Lassen Sie das Checkout-URL-Feld leer um die URL des Projektarchivs zu verwenden.
help_moved_settings: "Die Konfigurationsseite wurde nach {{link}} verschoben."
label_settings_location: "Administration -> Konfiguration -> Checkout"
\ No newline at end of file
en:
label_checkout: "Checkout"
setting_checkout_display_checkout_info: "Display checkout information"
setting_checkout_fixed_url: "Checkout URL"
setting_checkout_url_regex: "Regular expression"
setting_checkout_url_regex_replacement: "Replacement text"
setting_checkout_display_login: "Display Login"
setting_checkout_command: "Checkout command"
setting_checkout_use_zero_clipboard: "Display clipboard helper"
setting_checkout_overwrite_description: "Overwrite default description"
field_checkout_overwrite: "Overwrite default settings for checkout protocols"
field_checkout_display_command: "Display checkout command"
label_protocol_plural: "Protocols"
button_add_protocol: "Add Protocol"
label_access_type: 'This URL has <span id="checkout_access">{{type}}</span> access.'
label_access_read_only: 'Read-Only'
label_access_read_write: "Read+Write"
label_access_permission: "Depending on user's permissions"
label_append_path: "Append path"
label_display_login_none: "Do not show login or password"
label_display_login_username: "Show login but no password"
label_display_login_password: "Show login and password"
label_copy_to_clipboard: "Copy to clipboard"
help_checkout_protocols: |
The URLs in protocols are generated from applying the regular expression
and the replacement text to the original URL. The replacement text
supports back-references to braced expressions using the \1 notation.
help_repository_checkout_protocols: |
Leave the Checkout URL field empty to use the defined repository URL.
help_moved_settings: "The settings page has been moved to {{link}}."
label_settings_location: "Administration -> Settings -> Checkout"
es:
label_checkout: "Checkout"
setting_checkout_display_checkout_info: "Display checkout information"
setting_checkout_fixed_url: "URL de checkout"
setting_checkout_url_regex: "Expresion regular"
setting_checkout_url_regex_replacement: "Texto de remplazo"
setting_checkout_display_login: "Mostrar usuario"
setting_checkout_command: "Comando de checkout"
setting_checkout_use_zero_clipboard: "Display clipboard helper"
setting_checkout_overwrite_description: "Overwrite default description"
field_checkout_overwrite: "Overwrite default settings for checkout protocols"
field_checkout_display_command: "Display checkout command"
label_protocol_plural: "Protocolos"
button_add_protocol: "Crear Protocolo"
label_access_type: 'This URL has <span id="checkout_access">{{type}}</span> access.'
label_access_read_only: 'Read-Only'
label_access_read_write: "Read+Write"
label_access_permission: "Depending on user's permissions"
label_append_path: "Append path"
label_display_login_none: "No mostrar usuario o contraseña"
label_display_login_username: "Mostrar usuario pero no contraseña"
label_display_login_password: "Mostrar usuario y contraseña"
label_copy_to_clipboard: "Copy to clipboard"
help_checkout_protocols: |
The URLs in protocols are generated from applying the regular expression
and the replacement text to the original URL. The replacement text
supports back-references to braced expressions using the \1 notation.
help_repository_checkout_protocols: |
Leave the Checkout URL field empty to use the defined repository URL.
help_moved_settings: "The settings page has been moved to {{link}}."
label_settings_location: "Administration -> Settings -> Checkout"
fr:
label_checkout: "Dépôt"
setting_checkout_display_checkout_info: "Display checkout information"
setting_checkout_fixed_url: "URL du dépôt"
setting_checkout_url_regex: "Expression Régulière"
setting_checkout_url_regex_replacement: "Texte de substitution"
setting_checkout_display_login: "Affiche le login"
setting_checkout_command: "Checkout command"
setting_checkout_use_zero_clipboard: "Display clipboard helper"
setting_checkout_overwrite_description: "Overwrite default description"
field_checkout_overwrite: "Overwrite default settings for checkout protocols"
field_checkout_display_command: "Affiche l'URL du dépôt"
label_protocol_plural: "Protocoles"
button_add_protocol: "Créer un protocole"
label_access_type: 'This URL has <span id="checkout_access">{{type}}</span> access.'
label_access_read_only: 'Read-Only'
label_access_read_write: "Read+Write"
label_access_permission: "Depending on user's permissions"
label_append_path: "Append path"
label_display_login_none: "Ne pas afficher le login ni le mot de passe"
label_display_login_username: "Afficher le login, pas le mot de passe"
label_display_login_password: "Afficher le login et le mot de passe"
label_copy_to_clipboard: "Copy to clipboard"
help_checkout_protocols: |
The URLs in protocols are generated from applying the regular expression
and the replacement text to the original URL. The replacement text
supports back-references to braced expressions using the \1 notation.
help_repository_checkout_protocols: |
Leave the Checkout URL field empty to use the defined repository URL.
help_moved_settings: "The settings page has been moved to {{link}}."
label_settings_location: "Administration -> Settings -> Checkout"
it:
label_checkout: "Checkout"
setting_checkout_display_checkout_info: "Visualizza le informazioni sul checkout"
setting_checkout_fixed_url: "URL di checkout"
setting_checkout_url_regex: "Espressione regolare"
setting_checkout_url_regex_replacement: "Testo sostituito"
setting_checkout_display_login: "Login visualizzato"
setting_checkout_command: "Comando per il checkout"
setting_checkout_use_zero_clipboard: "Visualizza l'utility per la copia negli appunti"
setting_checkout_overwrite_description: "Sovrascrivi la descrizione predefinita"
field_checkout_overwrite: "Sovrascrivi le impostazioni di checkout predefinite"
field_checkout_display_command: "Visualizza il comando per il checkout"
label_protocol_plural: "Protocolli"
button_add_protocol: "Aggiungi Protocollo"
label_access_type: 'Questo URL ha accesso <span id="checkout_access">{{type}}</span>.'
label_access_read_only: 'Sola-Lettura'
label_access_read_write: "Lettura+Scrittura"
label_access_permission: "Dipende dai permessi dell'utente"
label_append_path: "Aggiungi percorso"
label_display_login_none: "Non mostrare il login e password"
label_display_login_username: "Mostra il login senza password"
label_display_login_password: "Mostra il login e la password"
label_copy_to_clipboard: "Copia negli appunti"
help_checkout_protocols: |
Gli URL dei protocolli sono generati applicando le espressioni regolari
ed effettuando la sostituzione dell'URL originale con il testo specificato.
Il testo per la sostituzione può contenere riferimenti a più match usando
la notazione \1 \2...
help_repository_checkout_protocols: |
Lascia il campo URL di checkout bianco per usare l'URL definito nel repository.
help_moved_settings: "La pagina delle impostazioni è stata spostata in {{link}}."
label_settings_location: "Amministrazione -> Impostazioni -> Checkout"
ja:
label_checkout: "Checkout"
setting_checkout_display_checkout_info: "Display checkout information"
setting_checkout_fixed_url: "チェックアウト URL"
setting_checkout_url_regex: "Regular expression"
setting_checkout_url_regex_replacement: "Replacement text"
setting_checkout_display_login: "ログインの表示"
setting_checkout_command: "Checkout command"
setting_checkout_use_zero_clipboard: "Display clipboard helper"
setting_checkout_overwrite_description: "Overwrite default description"
field_checkout_overwrite: "Overwrite default settings for checkout protocols"
field_checkout_display_command: "Display checkout command"
label_protocol_plural: "Protocols"
button_add_protocol: "Add Protocol"
label_access_type: 'This URL has <span id="checkout_access">{{type}}</span> access.'
label_access_read_only: 'Read-Only'
label_access_read_write: "Read+Write"
label_access_permission: "Depending on user's permissions"
label_append_path: "Append path"
label_display_login_none: "ログインとパスワードを非表示"
label_display_login_username: "ログインのみ表示"
label_display_login_password: "ログインとパスワードを表示"
label_copy_to_clipboard: "Copy to clipboard"
help_checkout_protocols: |
The URLs in protocols are generated from applying the regular expression
and the replacement text to the original URL. The replacement text
supports back-references to braced expressions using the \1 notation.
help_repository_checkout_protocols: |
Leave the Checkout URL field empty to use the defined repository URL.
help_moved_settings: "The settings page has been moved to {{link}}."
label_settings_location: "Administration -> Settings -> Checkout"
ko:
label_checkout: "Checkout"
setting_checkout_display_checkout_info: "Display checkout information"
setting_checkout_fixed_url: "체크 아웃 URL"
setting_checkout_url_regex: "정규식"
setting_checkout_url_regex_replacement: "대체 문자열"
setting_checkout_display_login: "로그인 표시"
setting_checkout_command: "Checkout command"
setting_checkout_use_zero_clipboard: "Display clipboard helper"
setting_checkout_overwrite_description: "Overwrite default description"
field_checkout_overwrite: "Overwrite default settings for checkout protocols"
field_checkout_display_command: "Display checkout command"
label_protocol_plural: "Protocols"
button_add_protocol: "Add Protocol"
label_access_type: 'This URL has <span id="checkout_access">{{type}}</span> access.'
label_access_read_only: 'Read-Only'
label_access_read_write: "Read+Write"
label_access_permission: "Depending on user's permissions"
label_append_path: "Append path"
label_display_login_none: "로그인과 비밀번호를 보여주지 않습니다."
label_display_login_username: "로그인은 보여주지만 비밀번호는 보여주지 않습니다."
label_display_login_password: "로그인과 비밀번호를 보여줍니다."
label_copy_to_clipboard: "Copy to clipboard"
help_checkout_protocols: |
The URLs in protocols are generated from applying the regular expression
and the replacement text to the original URL. The replacement text
supports back-references to braced expressions using the \1 notation.
help_repository_checkout_protocols: |
Leave the Checkout URL field empty to use the defined repository URL.
help_moved_settings: "The settings page has been moved to {{link}}."
label_settings_location: "Administration -> Settings -> Checkout"
nl:
label_checkout: "Checkout"
setting_checkout_display_checkout_info: "Checkout-informatie tonen"
setting_checkout_fixed_url: "Checkout-URL"
setting_checkout_url_regex: "Reguliere expressie"
setting_checkout_url_regex_replacement: "Vervangingstekst"
setting_checkout_display_login: "Geef login weer"
setting_checkout_command: "Checkout-opdracht"
setting_checkout_use_zero_clipboard: "Klembord-hulp tonen"
setting_checkout_overwrite_description: "Standaard omschrijving overschrijven"
field_checkout_overwrite: "Overschrijf standaard instellingen voor checkout-protocollen"
field_checkout_display_command: "Checkout-opdracht tonen"
label_protocol_plural: "Protocollen"
button_add_protocol: "Protocol toevoegen"
label_access_type: 'Deze URL heeft <span id="checkout_access">{{type}}</span> toegang.'
label_access_read_only: 'Alleen lezen'
label_access_read_write: "Lezen en schrijven"
label_access_permission: "Afhankelijk van gebruikersrechten"
label_append_path: "Pad toevoegen"
label_display_login_none: "Geen logingegevens tonen"
label_display_login_username: "Toon login zonder wachtwoord"
label_display_login_password: "Toon login en wachtwoord"
label_copy_to_clipboard: "Naar klembord kopiëren"
help_checkout_protocols: |
De URLs in protocollen worden samengesteld vanuit de originele URL, na
toepassing van de reguliere expressie en vervangingstekst. De vervangingstekst
ondersteunt referenties vanuit tussen haakjes geplaatste expressies
door middel van de \1 notatie.
help_repository_checkout_protocols: |
Laat het veld Checkout-URL leeg om de projectrepository te gebruiken.
help_moved_settings: "De instellingspagina is verplaatst naar {{link}}."
label_settings_location: "Administratie -> Instellingen -> Checkout"
\ No newline at end of file
pl:
label_checkout: "Pobieranie repozytorium"
setting_checkout_display_checkout_info: "Pokaż informację o możliwości pobrania repozytorium"
setting_checkout_fixed_url: "Adres URL pobierania repozytorium"
setting_checkout_url_regex: "Wyrażenie regularne"
setting_checkout_url_regex_replacement: "Wynikowy adres URL"
setting_checkout_display_login: "Pokaż dane logowania"
setting_checkout_command: "Komenda pobrania repozytorium"
setting_checkout_use_zero_clipboard: "Pokaż pomocnika schowka"
setting_checkout_overwrite_description: "Nadpisz domyślny opis"
field_checkout_overwrite: "Nadpisz domyślne ustawienia dla protokołów"
field_checkout_display_command: "Pokaż komendę pobrania repozytorium"
label_protocol_plural: "Protokoły"
button_add_protocol: "Dodaj protokół"
label_access_type: 'Ten adres URL ma dostęp <span id="checkout_access">{{type}}</span>.'
label_access_read_only: 'Tylko do odczytu'
label_access_read_write: "Odczyt+Zapis"
label_access_permission: "Zależne od uprawnień użytkownika"
label_append_path: "Dołącz ścieżkę"
label_display_login_none: "Nie pokazuj loginu ani hasła"
label_display_login_username: "Pokaż tylko login"
label_display_login_password: "Pokaż login i hasło"
label_copy_to_clipboard: "Kopiuj do schowka"
help_checkout_protocols: |
Wynikowe adresy URL w protokołach są generowane przez zamianę oryginalnego
adresu URL repozytorium na podstawie wyrażenia regularnego. Wynikowy adres
URL wspiera referencje do grup (tzw. back-references) używając notacji \1.
help_repository_checkout_protocols: |
Pozostaw adres URL pobierania repozytorium pusty aby uzyć adresu zdefiniowanego w ustawieniach projektu.
help_moved_settings: "Ustawienia zostały przeniesione do {{link}}."
label_settings_location: "Administracja -> Ustawienia -> Pobieranie repozytorium"
ro:
label_checkout: "Checkout"
setting_checkout_display_checkout_info: "Display checkout information"
setting_checkout_fixed_url: "URL-ul pentru checkout"
setting_checkout_url_regex: "Expresie regulata (regexp)"
setting_checkout_url_regex_replacement: "Text inlocuitor (regexp)"
setting_checkout_display_login: "Arata datele pentru autentificare"
setting_checkout_command: "Comanda de checkout"
setting_checkout_use_zero_clipboard: "Display clipboard helper"
setting_checkout_overwrite_description: "Overwrite default description"
field_checkout_overwrite: "Overwrite default settings for checkout protocols"
field_checkout_display_command: "Display checkout command"
label_protocol_plural: "Protocols"
button_add_protocol: "Add Protocol"
label_access_type: 'This URL has <span id="checkout_access">{{type}}</span> access.'
label_access_read_only: 'Read-Only'
label_access_read_write: "Read+Write"
label_access_permission: "Depending on user's permissions"
label_append_path: "Append path"
label_display_login_none: "Nu afisa username sau parola"
label_display_login_username: "Afiseaza username-ul, dar nu si parola"
label_display_login_password: "Afiseaza atat username-ul, cat si parola"
label_copy_to_clipboard: "Copy to clipboard"
help_checkout_protocols: |
The URLs in protocols are generated from applying the regular expression
and the replacement text to the original URL. The replacement text
supports back-references to braced expressions using the \1 notation.
help_repository_checkout_protocols: |
Leave the Checkout URL field empty to use the defined repository URL.
help_moved_settings: "The settings page has been moved to {{link}}."
label_settings_location: "Administration -> Settings -> Checkout"
class AddCheckoutUrlInfo < ActiveRecord::Migration
def self.up
add_column :repositories, :checkout_url_type, :string, :default => 'none', :null => false
add_column :repositories, :checkout_url, :string, :default => '', :null => false
end
def self.down
remove_column :repositories, :checkout_url_type
remove_column :repositories, :checkout_url
end
end
class AddDisplayLogin < ActiveRecord::Migration
def self.up
add_column :repositories, :display_login, :string, :default => 'none', :null => false
end
def self.down
remove_column :repositories, :display_login
end
end
class AddRenderLink < ActiveRecord::Migration
def self.up
add_column :repositories, :render_link, :boolean, :null => true
end
def self.down
remove_column :repositories, :render_link
end
end
\ No newline at end of file
class RemoveDefaults < ActiveRecord::Migration
def self.up
change_column :repositories, :checkout_url_type, :string, :default => nil, :null => true
change_column :repositories, :checkout_url, :string, :default => nil, :null => true
change_column :repositories, :display_login, :string, :default => nil, :null => true
end
def self.down
change_column :repositories, :checkout_url_type, :string, :default => 'none', :null => false
change_column :repositories, :checkout_url, :string, :default => '', :null => false
change_column :repositories, :display_login, :string, :default => 'none', :null => false
end
end
class AddOverwriteOption < ActiveRecord::Migration
def self.up
add_column :repositories, :checkout_url_overwrite, :boolean, :default => false, :null => false
# existing repositories are set to overwrite the default settings
# This is to keep continuity of settings.
Repository.reset_column_information
Repository.update_all({:checkout_url_overwrite, true})
end
def self.down
remove_column :repositories, :checkout_url_overwrite
end
end
class UpdateSettings < ActiveRecord::Migration
def self.up
settings = Setting.plugin_redmine_checkout
if settings['checkout_url_type'] == "overwritten"
settings['checkout_url_type'] = "generated"
end
if settings.has_key? "checkout_url_regex"
settings['checkout_url_regex_default'] = settings.delete("checkout_url_regex")
end
if settings.has_key? "checkout_url_regex_replacement"
settings['checkout_url_regex_replacement_default'] = settings.delete("checkout_url_regex_replacement")
end
Setting.plugin_redmine_checkout = settings
end
def self.down
settings = Setting.plugin_redmine_checkout
if settings['checkout_url_type'] == "generated"
settings['checkout_url_type'] = "overwritten"
end
if settings.has_key? "checkout_url_regex_default"
settings['checkout_url_regex'] = settings.delete("checkout_url_regex_default")
end
if settings.has_key? "checkout_url_regex_replacement_default"
settings['checkout_url_regex_replacement'] = settings.delete("checkout_url_regex_replacement_default")
end
Setting.plugin_redmine_checkout = settings
end
end
class RenameRenderLinkToRenderType < ActiveRecord::Migration
def self.up
render_link = Setting.plugin_redmine_checkout.delete 'render_link'
unless render_link.nil?
Setting.plugin_redmine_checkout['render_type'] = (render_link == 'true' ? 'link' : 'url')
Setting.plugin_redmine_checkout = Setting.plugin_redmine_checkout
end
add_column :repositories, :render_type, :string, :default => 'url', :null => false
Repository.update_all({:render_type => 'link'}, :render_link => true)
Repository.update_all({:render_type => 'url'}, ["render_link != ?", true])
remove_column :repositories, :render_link
end
def self.down
render_type = Setting.plugin_redmine_checkout.delete 'render_type'
unless render_type.nil?
Setting.plugin_redmine_checkout['render_link'] = (render_type == 'link' ? 'true' : 'false')
Setting.plugin_redmine_checkout = Setting.plugin_redmine_checkout
end
add_column :repositories, :render_link, :boolean, :null => true
Repository.update_all({:render_link => true}, :render_type => 'link')
Repository.update_all({:render_link => false}, ["render_type != ?", 'link'])
remove_column :repositories, :render_type
end
end
class ConsolidateRepositoryOptions < ActiveRecord::Migration
class Repository < ActiveRecord::Base
def self.inheritance_column
# disable single table inheritance
nil
end
serialize :checkout_settings, Hash
end
def self.up
add_column :repositories, :checkout_settings, :text
Repository.all.each do |r|
r.checkout_settings = {
"checkout_url_type" => r.checkout_url_type,
"checkout_url" => r.checkout_url,
"display_login" => r.display_login,
"render_type" => r.render_type,
"checkout_url_overwrite" => r.checkout_url_overwrite
}
r.save!
end
remove_column :repositories, :checkout_url_type
remove_column :repositories, :checkout_url
remove_column :repositories, :display_login
remove_column :repositories, :render_type
remove_column :repositories, :checkout_url_overwrite
end
def self.down
add_column :repositories, :checkout_url_type, :string, :default => nil, :null => true
add_column :repositories, :checkout_url, :string, :default => nil, :null => true
add_column :repositories, :display_login, :string, :default => nil, :null => true
add_column :repositories, :render_type, :string, :default => 'url', :null => false
add_column :repositories, :checkout_url_overwrite, :boolean, :default => false, :null => false
Repository.all.each do |r|
r.checkout_url_type = r.checkout_settings["checkout_url_type"]
r.checkout_url = r.checkout_settings["checkout_url"]
r.display_login = r.checkout_settings["display_login"]
r.render_link = r.checkout_settings["render_link"]
r.checkout_url_overwrite = r.checkout_settings["checkout_url_overwrite"]
r.save!
end
remove_column :repositories, :checkout_settings
end
end
class ApplySettingChanges < ActiveRecord::Migration
class Repository < ActiveRecord::Base
def self.inheritance_column
# disable single table inheritance
nil
end
def scm_name
self.type || 'Abstract'
end
serialize :checkout_settings, Hash
end
def self.up
default_commands = {
'Bazaar' => 'bzr checkout',
'Cvs' => 'cvs checkout',
'Darcs' => 'darcs get',
'Git' => 'git clone',
'Mercurial' => 'hg clone',
'Subversion' => 'svn checkout'
}
## First migrate the individual repositories
Repository.all.each do |r|
allow_subtree_checkout = ['Cvs', 'Subversion'].include? r.scm_name
protocol = case r.checkout_settings['checkout_url_type']
when 'none', 'generated'
nil
when 'original', 'overwritten'
HashWithIndifferentAccess.new({ "0" => HashWithIndifferentAccess.new({
:protocol => r.scm_name,
:command => Setting.plugin_redmine_checkout["checkout_cmd_#{r.scm_name}"] || default_commands[r.scm_name],
:regex => "",
:regex_replacement => "",
:fixed_url => (r.checkout_settings['checkout_url_type'] == 'original' ? (r.url || "") : r.checkout_settings["checkout_url"]),
:access => 'permission',
:append_path => (allow_subtree_checkout ? '1' : '0'),
:is_default => '1'})
})
end
r.checkout_settings = Hash.new({
'checkout_protocols' => protocol,
'checkout_description' => "The data contained in this repository can be downloaded to your computer using one of several clients.
Please see the documentation of your version control software client for more information.
Please select the desired protocol below to get the URL.",
'checkout_display_login' => (r.checkout_settings['display_login'] == 'none' ? '' : r.checkout_settings['display_login']),
'checkout_overwrite' => (r.checkout_settings['checkout_url_overwrite'] == 'true') ? '1': '0',
'checkout_display_command' => (r.checkout_settings["render_type"].to_s == 'cmd') ? '1' : '0'
})
r.save!
end
## Then the global settings
settings = HashWithIndifferentAccess.new({
'display_login' => Setting.plugin_redmine_checkout['display_login'],
'use_zero_clipboard' => '1',
'display_checkout_info' => (Setting.plugin_redmine_checkout['checkout_url_type'] == 'none' ? '0' : '1'),
'description_Abstract' => <<-EOF
The data contained in this repository can be downloaded to your computer using one of several clients.
Please see the documentation of your version control software client for more information.
Please select the desired protocol below to get the URL.
EOF
})
default_commands.keys.each do |scm|
settings["description_#{scm}"] = ''
settings["overwrite_description_#{scm}"] = '0'
display_command = (Setting.plugin_redmine_checkout["render_type"].to_s == 'cmd') ? '1' : '0'
settings["display_command_#{scm}"] = display_command
case Setting.plugin_redmine_checkout['checkout_url_type']
when 'generated', 'none':
regex = Setting.plugin_redmine_checkout["checkout_url_regex_#{scm}"]
replacement = Setting.plugin_redmine_checkout["checkout_url_regex_replacement_#{scm}"]
when 'original':
regex = ''
replacement = ''
end
settings["protocols_#{scm}"] = HashWithIndifferentAccess.new({
# access can be one of
# read+write => this protocol always allows read/write access
# read-only => this protocol always allows read access only
# permission => Access depends on redmine permissions
'0' => HashWithIndifferentAccess.new({
:protocol => scm,
:command => Setting.plugin_redmine_checkout["checkout_cmd_#{scm}"] || default_commands[scm],
:regex => regex,
:regex_replacement => replacement,
:fixed_url => '',
:access => 'permission',
:append_path => (['Cvs', 'Subversion'].include?(scm) ? '1' : '0'),
:is_default => '1'
})
})
end
Setting.plugin_redmine_checkout = settings
end
def self.down
raise ActiveRecord::IrreversibleMigration.new "Sorry, there is no down migration yet. If you really need one, please create an issue on http://dev.holgerjust.de/projects/redmine-checkout"
end
end
\ No newline at end of file
class ChangeProtocolStorageFromHashToArray < ActiveRecord::Migration
class Repository < ActiveRecord::Base
def self.inheritance_column
# disable single table inheritance
nil
end
def scm_name
self.type || 'Abstract'
end
serialize :checkout_settings, Hash
end
def self.up
## First migrate the individual repositories
Repository.all.each do |r|
next unless r.checkout_settings['checkout_protocols'].is_a? Hash
r.checkout_settings['checkout_protocols'] = r.checkout_settings['checkout_protocols'].sort{|(ak,av),(bk,bv)|ak<=>bk}.collect{|id,protocol| protocol}
r.save!
end
## Then the global settings
settings = Setting.plugin_redmine_checkout
settings.keys.grep(/^protocols_/).each do |protocols|
next unless settings[protocols].is_a? Hash
settings[protocols] = settings[protocols].sort{|(ak,av),(bk,bv)|ak<=>bk}.collect{|id,protocol| protocol}
end
Setting.plugin_redmine_checkout = settings
end
def self.down
## First migrate the individual repositories
Repository.all.each do |r|
next unless r.checkout_settings['checkout_protocols'].is_a? Hash
r.checkout_settings['checkout_protocols'] = r.checkout_settings['checkout_protocols'].inject(HashWithIndifferentAccess.new) do |result, p|
result[result.length.to_s] = p
end
r.save!
end
## Then the global settings
settings = Setting.plugin_redmine_checkout
settings.keys.grep(/^protocols_/).each do |protocols|
next unless r.checkout_settings['checkout_protocols'].is_a? Hash
settings[protocols] = settings[protocols].inject(HashWithIndifferentAccess.new) do |result, p|
result[result.length.to_s] = p
end
end
Setting.plugin_redmine_checkout = settings
end
end
\ No newline at end of file
Copyright (c) 2009 Holger Just
Permission is hereby granted, free of charge, to any person
obtaining a copy of this software and associated documentation
files (the "Software"), to deal in the Software without
restriction, including without limitation the rights to use,
copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following
conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.
require 'redmine'
require 'dispatcher'
Dispatcher.to_prepare do
# Patches
require_dependency 'checkout/settings_controller_patch'
require_dependency 'checkout/repositories_helper_patch'
require_dependency 'checkout/repository_patch'
require_dependency 'checkout/settings_helper_patch'
require_dependency 'checkout/setting_patch'
end
# Hooks
require 'checkout/repository_hooks'
Redmine::Plugin.register :redmine_checkout do
name 'Redmine Checkout plugin'
url 'http://dev.holgerjust.de/projects/redmine-checkout'
author 'Holger Just'
author_url 'http://meine-er.de'
description 'Add links to the actual repository to the repository view.'
version '0.5'
requires_redmine :version_or_higher => '0.9'
settings_defaults = HashWithIndifferentAccess.new({
'display_login' => nil,
'use_zero_clipboard' => '1',
'display_checkout_info' => '1',
'description_Abstract' => <<-EOF
The data contained in this repository can be downloaded to your computer using one of several clients.
Please see the documentation of your version control software client for more information.
Please select the desired protocol below to get the URL.
EOF
})
# this is needed for setting the defaults
require 'checkout/repository_patch'
CheckoutHelper.supported_scm.each do |scm|
klazz = "Repository::#{scm}".constantize
settings_defaults["description_#{scm}"] = ''
settings_defaults["overwrite_description_#{scm}"] = '0'
settings_defaults["display_command_#{scm}"] = '0'
# access can be one of
# read+write => this protocol always allows read/write access
# read-only => this protocol always allows read access only
# permission => Access depends on redmine permissions
settings_defaults["protocols_#{scm}"] = [HashWithIndifferentAccess.new({
:protocol => scm,
:command => klazz.checkout_default_command,
:regex => '',
:regex_replacement => '',
:fixed_url => '',
:access => 'permission',
:append_path => (klazz.allow_subtree_checkout? ? '1' : '0'),
:is_default => '1'
})]
end
settings :default => settings_defaults, :partial => 'settings/redmine_checkout'
Redmine::WikiFormatting::Macros.register do
desc <<-EOF
Creates a checkout link to the actual repository. Example:
use the default checkout protocol !{{repository}}
or use a specific protocol !{{repository(SVN)}}
or use the checkout protocol of a specific specific project: !{{repository(projectname:SVN)}}"
EOF
macro :repository do |obj, args|
proto = args.first
if proto.to_s =~ %r{^([^\:]+)\:(.*)$}
project_identifier, proto = $1, $2
project = Project.find_by_identifier(project_identifier) || Project.find_by_name(project_identifier)
else
project = @project
end
if project && project.repository
protocols = project.repository.checkout_protocols.select{|p| p.access_rw(User.current)}
if proto.present?
proto_obj = protocols.find{|p| p.protocol.downcase == proto.downcase}
else
proto_obj = protocols.find(&:default?) || protocols.first
end
end
raise "Checkout protocol #{proto} not found" unless proto_obj
cmd = (project.repository.checkout_display_command? && proto_obj.command.present?) ? proto_obj.command.strip + " " : ""
cmd + link_to(proto_obj.url, proto_obj.url)
end
end
end
\ No newline at end of file
module Checkout
class <<self
def awesome?
# Yes, this plugin is awesome!
true
end
end
class Protocol
attr_accessor :protocol, :regex, :regex_replacement, :access, :repository
attr_writer :default, :command, :fixed_url, :append_path
def initialize(args={})
args = args.dup
@protocol = args.delete :protocol
@command = args.delete :command # optional, if not set the default from the repo is used
# either a fixed url
@fixed_url = args.delete :fixed_url
# or a regex
@regex = args.delete :regex
@regex_replacement = args.delete :regex_replacement
@access = args.delete :access
@append_path = args.delete :append_path
@default = args.delete :is_default
@repository = args.delete :repository
end
def full_command(path = "")
cmd = ""
if repository.checkout_display_command?
cmd = self.command.present? ? self.command.strip + " " : ""
end
cmd + URI.escape(self.url(path))
end
def default?
@default.to_i > 0
end
def command
@command || self.repository && self.repository.checkout_default_command || ""
end
def append_path?
@append_path.to_i > 0
end
def access_rw(user)
# reduces the three available access levels 'read+write', 'read-only' and 'permission'
# to 'read+write' and 'read-only' and nil (not allowed)
@access_rw ||= {}
return @access_rw[user] if @access_rw.key? user
@access_rw[user] = case access
when 'permission'
case
when user.allowed_to?(:commit_access, repository.project) && user.allowed_to?(:browse_repository, repository.project)
'read+write'
when user.allowed_to?(:browse_repository, repository.project)
'read-only'
else
nil
end
else
@access
end
end
def access_label(user)
case access_rw(user)
when 'read+write': :label_access_read_write
when 'read-only': :label_access_read_only
end
end
def fixed_url
@fixed_url.present? ? @fixed_url : begin
if (regex.blank? || regex_replacement.blank?)
repository.url
else
repository.url.gsub(Regexp.new(regex), regex_replacement)
end
end
rescue RegexpError
repository.url || ""
end
def url(path = "")
return "" unless repository
url = fixed_url.sub(/\/+$/, "")
if repository.allow_subtree_checkout? && self.append_path? && path.present?
url = "#{url}/#{path}"
end
if repository.checkout_display_login?
begin
uri = URI.parse url
unless uri.scheme == 'file'
# file URIs can't possibly contain any username / password info
# And URI.parse does not properly reconstruct the URL...
(uri.user = repository.login) if repository.login
(uri.password = repository.password) if (repository.checkout_display_login == 'password' && repository.login && repository.password)
url = uri.to_s
end
rescue URI::InvalidURIError
end
end
url
end
end
end
\ No newline at end of file
require_dependency 'repositories_helper'
module Checkout
module RepositoriesHelperPatch
def self.included(base) # :nodoc:
base.send(:include, InstanceMethods)
base.class_eval do
alias_method_chain :repository_field_tags, :checkout
alias_method_chain :scm_select_tag, :javascript
end
end
module InstanceMethods
def repository_field_tags_with_checkout(form, repository)
tags = repository_field_tags_without_checkout(form, repository) || ""
return tags if repository.class.name == "Repository"
tags + @controller.send(:render_to_string, :partial => 'projects/settings/repository_checkout', :locals => {:form => form, :repository => repository, :scm => repository.scm_name})
end
def scm_select_tag_with_javascript(*args)
content_for :header_tags do
javascript_include_tag('subform', :plugin => 'redmine_checkout') +
stylesheet_link_tag('checkout', :plugin => 'redmine_checkout')
end
scm_select_tag_without_javascript(*args)
end
end
end
end
RepositoriesHelper.send(:include, Checkout::RepositoriesHelperPatch)
module Checkout
class RepositoryHooks < Redmine::Hook::ViewListener
# Renders the checkout URL
#
# Context:
# * :project => Current project
# * :repository => Current Repository
#
def view_repositories_show_contextual(context={})
if context[:repository].present? && Setting.checkout_display_checkout_info?
protocols = context[:repository].checkout_protocols.select do |p|
p.access_rw(User.current)
end
path = context[:controller].instance_variable_get("@path")
if path && context[:controller].instance_variable_get("@entry")
# a single file is showing, so we return only the directory
path = File.dirname(path)
end
default = protocols.find(&:default?) || protocols.first
context.merge!({
:protocols => protocols,
:default_protocol => default,
:checkout_path => path
})
options = {:partial => "redmine_checkout_hooks/view_repositories_show_contextual"}
context[:controller].send(:render_to_string, {:locals => context}.merge(options))
end
end
end
end
\ No newline at end of file
require_dependency 'repository'
require_dependency 'checkout_helper'
module Checkout
module RepositoryPatch
def self.included(base) # :nodoc:
base.extend(ClassMethods)
base.send(:include, InstanceMethods)
base.class_eval do
unloadable
serialize :checkout_settings, Hash
end
end
module ClassMethods
def allow_subtree_checkout?
# default implementation
false
end
def checkout_default_command
# default implementation
""
end
end
module InstanceMethods
def after_initialize
self.checkout_settings ||= {}
end
def checkout_overwrite=(value)
checkout_settings['checkout_overwrite'] = value
end
def checkout_overwrite
(checkout_settings['checkout_overwrite'].to_i > 0) ? '1' : '0'
end
def checkout_overwrite?
self.scm_name != 'Abstract' && checkout_overwrite.to_i > 0
end
def checkout_description=(value)
checkout_settings['checkout_description'] = value
end
def checkout_description
if checkout_overwrite?
checkout_settings['checkout_description']
else
if CheckoutHelper.supported_scm.include?(scm_name) && Setting.send("checkout_overwrite_description_#{scm_name}?")
Setting.send("checkout_description_#{scm_name}")
else
Setting.send("checkout_description_Abstract")
end
end
end
def checkout_protocols
@checkout_protocols ||= begin
if CheckoutHelper.supported_scm.include? scm_name
if checkout_overwrite?
protocols = checkout_settings['checkout_protocols'] || []
else
protocols = Setting.send("checkout_protocols_#{scm_name}") || []
end
else
protocols = []
end
protocols.collect do |p|
Checkout::Protocol.new p.merge({:repository => self})
end
end
end
def checkout_protocols=(value)
# value is an Array or a Hash
if value.is_a? Hash
value = value.dup.delete_if {|id, protocol| id.to_i < 0 }
value = value.sort{|(ak,av),(bk,bv)|ak<=>bk}.collect{|id,protocol| protocol}
end
checkout_settings['checkout_protocols'] = value
end
def checkout_display_login
return "" unless self.scm_name == "Subversion"
if checkout_overwrite?
result = checkout_settings['checkout_display_login']
else
result = Setting.checkout_display_login
end
end
def checkout_display_login?
!checkout_display_login.blank?
end
def checkout_display_login=(value)
value = nil unless self.scm_name == "Subversion"
checkout_settings['checkout_display_login'] = value
end
def checkout_display_command?
checkout_display_command.to_i > 0
end
def checkout_display_command=(value)
checkout_settings['checkout_display_command'] = value
end
def checkout_display_command
if checkout_overwrite?
checkout_settings['checkout_display_command']
else
Setting.send("checkout_display_command_#{scm_name}")
end
end
def allow_subtree_checkout?
self.class.allow_subtree_checkout?
end
def checkout_default_command
self.class.checkout_default_command
end
end
end
end
Repository.send(:include, Checkout::RepositoryPatch)
subtree_checkout_repos = ["Subversion", "Cvs"]
commands = {
'Bazaar' => 'bzr checkout',
'Cvs' => 'cvs checkout',
'Darcs' => 'darcs get',
'Git' => 'git clone',
'Mercurial' => 'hg clone',
'Subversion' => 'svn checkout'
}
CheckoutHelper.supported_scm.each do |scm|
require_dependency "repository/#{scm.underscore}"
cls = Repository.const_get(scm)
allow_subtree_checkout = ""
if subtree_checkout_repos.include? scm
allow_subtree_checkout = <<-EOS
def allow_subtree_checkout?
true
end
EOS
end
checkout_command = ""
if commands[scm]
checkout_command = <<-EOS
def checkout_default_command
'#{commands[scm]}'
end
EOS
end
class_mod = Module.new
class_mod.module_eval(<<-EOF
def self.included(base)
base.extend ChildClassMethods
base.class_eval do
unloadable
serialize :checkout_settings, Hash
end
end
module ChildClassMethods
#{allow_subtree_checkout}
#{checkout_command}
end
EOF
)
cls.send(:include, class_mod)
end
require_dependency 'setting'
module Checkout
module SettingPatch
def self.included(base) # :nodoc:
base.extend(ClassMethods)
base.class_eval do
unloadable
# Defines getter and setter for each setting
# Then setting values can be read using: Setting.some_setting_name
# or set using Setting.some_setting_name = "some value"
Redmine::Plugin.find(:redmine_checkout).settings[:default].keys.each do |name|
if name.start_with?('protocols_')
default = "[]"
else
default = <<-END_SRC
begin
default = Setting.available_settings['plugin_redmine_checkout']['default']['#{name}']
# perform a deep copy of the default
Marshal::load(Marshal::dump(default))
end
END_SRC
end
src = <<-END_SRC
def self.checkout_#{name}
self.plugin_redmine_checkout[:#{name}] || #{default}
end
def self.checkout_#{name}?
self.checkout_#{name}.to_i > 0
end
def self.checkout_#{name}=(value)
setting = Setting.plugin_redmine_checkout
setting[:#{name}] = value
Setting.plugin_redmine_checkout = setting
end
END_SRC
class_eval src, __FILE__, __LINE__
end
class <<self
alias_method :store_without_checkout, :[]=
alias_method :[]=, :store_with_checkout
alias_method :retrieve_without_checkout, :[]
alias_method :[], :retrieve_with_checkout
end
end
end
module ClassMethods
def store_with_checkout(name, value)
if name.to_s.starts_with? "checkout_"
self.send("#{name}=", value)
else
store_without_checkout(name, value)
end
end
def retrieve_with_checkout(name)
if name.to_s.starts_with? "checkout_"
self.send("#{name}")
else
retrieve_without_checkout(name)
end
end
end
end
end
Setting.send(:include, Checkout::SettingPatch)
\ No newline at end of file
require_dependency 'settings_controller'
module Checkout
module SettingsControllerPatch
def self.included(base) # :nodoc:
base.send(:include, InstanceMethods)
base.class_eval do
unloadable
alias_method_chain :edit, :checkout
end
end
module InstanceMethods
def edit_with_checkout
if request.post? && params['tab'] == 'checkout'
if params[:settings] && params[:settings].is_a?(Hash)
settings = HashWithIndifferentAccess.new
(params[:settings] || {}).each do |name, value|
if name = name.to_s.slice(/checkout_(.+)/, 1)
case value
when Array
# remove blank values in array settings
value.delete_if {|v| v.blank? }
when Hash
# change protocols hash to array.
value = value.sort{|(ak,av),(bk,bv)|ak<=>bk}.collect{|id,protocol| protocol} if name.start_with? "protocols_"
end
settings[name.to_sym] = value
end
end
Setting.plugin_redmine_checkout = settings
params[:settings] = {}
end
end
edit_without_checkout
end
end
end
end
SettingsController.send(:include, Checkout::SettingsControllerPatch)
require_dependency 'settings_helper'
module Checkout
module SettingsHelperPatch
def self.included(base) # :nodoc:
base.send(:include, InstanceMethods)
base.class_eval do
alias_method_chain :administration_settings_tabs, :checkout
end
end
module InstanceMethods
def administration_settings_tabs_with_checkout
tabs = administration_settings_tabs_without_checkout
tabs << {:name => 'checkout', :partial => 'settings/checkout', :label => :label_checkout}
end
end
end
end
SettingsHelper.send(:include, Checkout::SettingsHelperPatch)
module CheckoutHelper
class <<self
def supported_scm
Object.const_defined?("REDMINE_SUPPORTED_SCM") ? REDMINE_SUPPORTED_SCM : Redmine::Scm::Base.all
end
end
end
namespace :redmine do
namespace :plugins do
namespace :redmine_checkout do
desc "Sets all repositories to inherit the default setting for the checkout URL."
task :set_default => :environment do
Repository.all.each{|r| r.update_attributes(:checkout_overwrite => "0")}
end
end
end
end
begin
require "spec/rake/spectask"
namespace :spec do
namespace :plugins do
desc "Runs the examples for redmine_checkout"
Spec::Rake::SpecTask.new(:redmine_checkout) do |t|
t.spec_opts = ['--options', "\"#{RAILS_ROOT}/spec/spec.opts\""]
t.spec_files = FileList['vendor/plugins/redmine_checkout/spec/**/*_spec.rb']
end
end
end
task :spec => "spec:plugins:redmine_checkout"
rescue LoadError
end
\ No newline at end of file
require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
describe RepositoriesController do
fixtures :settings, :repositories, :projects, :roles, :users, :enabled_modules
integrate_views
before(:each) do
Setting.default_language = 'en'
User.current = nil
end
def get_repo
get :show, :id => 1
end
it "should display the protocol selector" do
get_repo
response.should be_success
response.should render_template('show')
response.should have_tag('ul#checkout_protocols') do
with_tag('a[id=?][href=?]', 'checkout_protocol_subversion', "file:///#{RAILS_ROOT.gsub(%r{config\/\.\.}, '')}/tmp/test/subversion_repository")
with_tag('a[id=?][href=?]', 'checkout_protocol_svn+ssh', 'svn+ssh://svn.foo.bar/svn/subversion_repository')
end
end
it "should display the description" do
get_repo
response.should be_success
response.should render_template('show')
response.should have_tag('div.repository-info', /Please select the desired protocol below to get the URL/)
end
it 'should respect the use zero clipboard option' do
Setting.checkout_use_zero_clipboard = '1'
get_repo
response.should be_success
response.should render_template('show')
response.should have_tag('script[src*=?]', 'ZeroClipboard')
Setting.checkout_use_zero_clipboard = '0'
get_repo
response.should be_success
response.should render_template('show')
response.should_not have_tag('script[src*=]', 'ZeroClipboard')
end
end
---
enabled_modules_001:
name: repository
project_id: 1
id: 1
---
projects_001:
created_on: 2006-07-19 19:13:59 +02:00
name: eCookbook
updated_on: 2006-07-19 22:53:01 +02:00
id: 1
description: Recipes management application
homepage: http://ecookbook.somenet.foo/
is_public: true
identifier: ecookbook
parent_id:
lft: 1
rgt: 10
---
svn:
project_id: 1
url: file:///<%= RAILS_ROOT.gsub(%r{config\/\.\.}, '') %>/tmp/test/subversion_repository
id: 1
root_url: file:///<%= RAILS_ROOT.gsub(%r{config\/\.\.}, '') %>/tmp/test/subversion_repository
password: ""
login: ""
type: Subversion
---
roles_001:
name: Manager
id: 1
builtin: 0
permissions: |
---
- :add_project
- :edit_project
- :manage_members
- :manage_versions
- :manage_categories
- :view_issues
- :add_issues
- :edit_issues
- :manage_issue_relations
- :manage_subtasks
- :add_issue_notes
- :move_issues
- :delete_issues
- :view_issue_watchers
- :add_issue_watchers
- :delete_issue_watchers
- :manage_public_queries
- :save_queries
- :view_gantt
- :view_calendar
- :log_time
- :view_time_entries
- :edit_time_entries
- :delete_time_entries
- :manage_news
- :comment_news
- :view_documents
- :manage_documents
- :view_wiki_pages
- :export_wiki_pages
- :view_wiki_edits
- :edit_wiki_pages
- :delete_wiki_pages_attachments
- :protect_wiki_pages
- :delete_wiki_pages
- :rename_wiki_pages
- :add_messages
- :edit_messages
- :delete_messages
- :manage_boards
- :view_files
- :manage_files
- :browse_repository
- :manage_repository
- :view_changesets
- :manage_project_activities
position: 1
roles_002:
name: Developer
id: 2
builtin: 0
permissions: |
---
- :edit_project
- :manage_members
- :manage_versions
- :manage_categories
- :view_issues
- :add_issues
- :edit_issues
- :manage_issue_relations
- :manage_subtasks
- :add_issue_notes
- :move_issues
- :delete_issues
- :view_issue_watchers
- :save_queries
- :view_gantt
- :view_calendar
- :log_time
- :view_time_entries
- :edit_own_time_entries
- :manage_news
- :comment_news
- :view_documents
- :manage_documents
- :view_wiki_pages
- :view_wiki_edits
- :edit_wiki_pages
- :protect_wiki_pages
- :delete_wiki_pages
- :add_messages
- :edit_own_messages
- :delete_own_messages
- :manage_boards
- :view_files
- :manage_files
- :browse_repository
- :view_changesets
position: 2
roles_003:
name: Reporter
id: 3
builtin: 0
permissions: |
---
- :edit_project
- :manage_members
- :manage_versions
- :manage_categories
- :view_issues
- :add_issues
- :edit_issues
- :manage_issue_relations
- :add_issue_notes
- :move_issues
- :view_issue_watchers
- :save_queries
- :view_gantt
- :view_calendar
- :log_time
- :view_time_entries
- :manage_news
- :comment_news
- :view_documents
- :manage_documents
- :view_wiki_pages
- :view_wiki_edits
- :edit_wiki_pages
- :delete_wiki_pages
- :add_messages
- :manage_boards
- :view_files
- :manage_files
- :browse_repository
- :view_changesets
position: 3
roles_004:
name: Non member
id: 4
builtin: 1
permissions: |
---
- :view_issues
- :add_issues
- :edit_issues
- :manage_issue_relations
- :add_issue_notes
- :move_issues
- :save_queries
- :view_gantt
- :view_calendar
- :log_time
- :view_time_entries
- :comment_news
- :view_documents
- :manage_documents
- :view_wiki_pages
- :view_wiki_edits
- :edit_wiki_pages
- :add_messages
- :view_files
- :manage_files
- :browse_repository
- :view_changesets
position: 4
roles_005:
name: Anonymous
id: 5
builtin: 2
permissions: |
---
- :view_issues
- :add_issue_notes
- :view_gantt
- :view_calendar
- :view_time_entries
- :view_documents
- :view_wiki_pages
- :view_wiki_edits
- :view_files
- :browse_repository
- :view_changesets
position: 5
---
settings:
name: plugin_redmine_checkout
value: |
--- !map:HashWithIndifferentAccess
display_checkout_info: "1"
description_Abstract: |
The data contained in this repository can be downloaded to your computer using one of several clients.
Please see the documentation of your version control software client for more information.
Please select the desired protocol below to get the URL.
display_command_Bazaar: '1'
use_zero_clipboard: "1"
overwrite_description_Bazaar: "0"
description_Bazaar: ""
display_command_Bazaar: '1'
protocols_Bazaar:
- !map:HashWithIndifferentAccess
command: "bzr checkout"
regex: ""
regex_replacement: ""
read_write: readwrite
append_path: "0"
is_default: "1"
protocol: Bazaar
overwrite_description_Cvs: "0"
description_Cvs: ""
display_command_Cvs: '1'
protocols_Cvs:
- !map:HashWithIndifferentAccess
command: "cvs checkout"
regex: ""
regex_replacement: ""
read_write: readwrite
append_path: "0"
is_default: "1"
protocol: Cvs
overwrite_description_Darcs: "0"
description_Darcs: ""
display_command_Darcs: '1'
protocols_Darcs:
- !map:HashWithIndifferentAccess
command: "darcs get"
regex: ""
regex_replacement: ""
read_write: readwrite
append_path: "0"
is_default: "1"
protocol: Darcs
overwrite_description_Filesystem: "0"
description_Filesystem: ""
display_command_Filesystem: '1'
protocols_Filesystem:
- !map:HashWithIndifferentAccess
command: ""
regex: ""
append_path: "0"
is_default: "1"
protocol: Filesystem
access: read+write
regex_replacement: ""
overwrite_description_Git: "0"
description_Git: ""
display_command_Git: '1'
protocols_Git:
- !map:HashWithIndifferentAccess
command: "git clone"
regex: ""
append_path: "0"
is_default: "1"
protocol: Git
access: read+write
regex_replacement: ""
overwrite_description_Mercurial: "0"
description_Mercurial: ""
display_command_Mercurial: '1'
protocols_Mercurial:
- !map:HashWithIndifferentAccess
command: "hg clone"
regex: ""
append_path: "0"
is_default: "1"
protocol: Mercurial
access: read+write
regex_replacement: ""
display_login: username
overwrite_description_Subversion: "0"
description_Subversion: ""
display_command_Subversion: '1'
protocols_Subversion:
- !map:HashWithIndifferentAccess
command: "svn checkout"
regex: foo
append_path: "1"
is_default: "1"
protocol: Subversion
access: permission
regex_replacement: bar
- !map:HashWithIndifferentAccess
command: "svn co"
regex: "^.*?([^/]+)/?$"
append_path: "1"
is_default: "0"
protocol: SVN+SSH
access: read-only
regex_replacement: svn+ssh://svn.foo.bar/svn/\1
- !map:HashWithIndifferentAccess
command: "svn checkout"
append_path: "0"
is_default: "0"
regex: ""
protocol: Root
access: read+write
regex_replacement: ""
require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
describe "Macros" do
fixtures :settings, :repositories, :projects, :enabled_modules
include ERB::Util
include ApplicationHelper
include ActionView::Helpers::TextHelper
include ActionView::Helpers::TagHelper
include ActionView::Helpers::UrlHelper
before(:each) do
Setting.checkout_display_command_Subversion = '0'
@project = projects :projects_001
end
it "should display default checkout url" do
text = "{{repository}}"
url = "file:///#{RAILS_ROOT.gsub(%r{config\/\.\.}, '')}/tmp/test/subversion_repository"
textilizable(text).should eql "<p><a href=\"#{url}\">#{url}</a></p>"
end
it "should display forced checkout url" do
text = "{{repository(svn+ssh)}}"
url = 'svn+ssh://svn.foo.bar/svn/subversion_repository'
textilizable(text).should eql "<p><a href=\"#{url}\">#{url}</a></p>"
end
it "should fail without set project" do
@project = nil
text = "{{repository(svn+ssh)}}"
textilizable(text).should eql "<p><div class=\"flash error\">Error executing the <strong>repository</strong> macro (Checkout protocol svn+ssh not found)</div></p>"
end
it "should display checkout url from stated project" do
@project = nil
text = "{{repository(ecookbook:svn+ssh)}}"
url = 'svn+ssh://svn.foo.bar/svn/subversion_repository'
textilizable(text).should eql "<p><a href=\"#{url}\">#{url}</a></p>"
end
it "should display command" do
Setting.checkout_display_command_Subversion = '1'
text = "{{repository(svn+ssh)}}"
url = 'svn+ssh://svn.foo.bar/svn/subversion_repository'
textilizable(text).should eql "<p>svn co <a href=\"#{url}\">#{url}</a></p>"
end
end
require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
describe Checkout::Protocol do
fixtures :settings, :repositories, :projects, :enabled_modules
before(:each) do
@admin = User.new
@admin.admin = true
@user = User.new
@repo = repositories :svn
@repo.url = "http://example.com/svn/testrepo"
end
it "should use regexes for generated URL" do
protocol = @repo.checkout_protocols.find{|r| r.protocol == "SVN+SSH"}
protocol.url.should eql "svn+ssh://svn.foo.bar/svn/testrepo"
end
it "should resolve access properties" do
protocol = @repo.checkout_protocols.find{|r| r.protocol == "Subversion"}
protocol.access.should eql "permission"
protocol.access_rw(@admin).should eql "read+write"
User.current = @user
protocol.access_rw(@user).should eql "read-only"
end
it "should display the checkout command" do
subversion = @repo.checkout_protocols.find{|r| r.protocol == "Subversion"}
svn_ssh = @repo.checkout_protocols.find{|r| r.protocol == "SVN+SSH"}
subversion.command.should eql "svn checkout"
svn_ssh.command.should eql "svn co"
end
it "should respect display login settings" do
protocols = @repo.checkout_protocols
@repo.login = "der_baer"
@repo.checkout_overwrite = "1"
@repo.checkout_protocols = protocols
protocol = @repo.checkout_protocols.find{|r| r.protocol == "Root"}
@repo.checkout_display_login = ""
protocol.url.should eql "http://example.com/svn/testrepo"
@repo.checkout_display_login = "username"
protocol.url.should eql "http://der_baer@example.com/svn/testrepo"
end
end
require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
describe Repository do
fixtures :settings, :repositories
describe "initialize" do
before(:each) do
@repo = Repository.new()
end
it "should properly set default values" do
@repo.checkout_overwrite?.should be_false
@repo.checkout_description.should match /Please select the desired protocol below to get the URL/
@repo.checkout_display_login?.should be_false # no subversion repo
@repo.allow_subtree_checkout?.should be_false
@repo.checkout_protocols.should eql []
end
end
describe "subtree checkout" do
before(:each) do
@svn = Repository::Subversion.new
@git = Repository::Git.new
end
it "should be allowed on subversion" do
@svn.allow_subtree_checkout?.should eql true
end
it "should only be possible if checked" do
end
it "should be forbidden on git" do
@git.allow_subtree_checkout?.should eql false
end
end
describe "extensions" do
before(:each) do
@repo = Repository::Subversion.new
end
it "should provide protocols" do
protocols = @repo.checkout_protocols
protocols[0].protocol.should eql "Subversion"
protocols[1].protocol.should eql "SVN+SSH"
protocols[2].protocol.should eql "Root"
end
end
end
\ No newline at end of file
require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
describe Setting do
fixtures :settings
before(:each) do
Setting.default_language = 'en'
end
it "should recognize checkout methods" do
Setting.checkout_display_checkout_info.should eql Setting.plugin_redmine_checkout['display_checkout_info']
Setting.checkout_display_checkout_info.should eql Setting.plugin_redmine_checkout[:display_checkout_info]
end
end
require File.dirname(__FILE__) + '/spec_helper'
describe Class do
it "should be a class of Class" do
Class.class.should eql(Class)
end
it "should be awesome" do
Checkout.awesome?.should be_true
end
end
--colour
--format
progress
--loadby
mtime
--reverse
--backtrace
\ No newline at end of file
ENV['RAILS_ENV'] ||= 'test'
# prevent case where we are using rubygems and test-unit 2.x is installed
begin
require 'rubygems'
gem "test-unit", "~> 1.2.3"
rescue LoadError
end
begin
require "config/environment" unless defined? RAILS_ROOT
require RAILS_ROOT + '/spec/spec_helper'
rescue LoadError => error
puts <<-EOS
You need to install rspec in your Redmine project.
Please execute the following code:
gem install rspec-rails
script/generate rspec
EOS
raise error
end
Fixtures.create_fixtures File.join(File.dirname(__FILE__), "fixtures"), ActiveRecord::Base.connection.tables
require File.join(File.dirname(__FILE__), "..", "init.rb")
\ No newline at end of file
Copyright (c) 2010 Jakob Skjerning
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
= Redmine Github Hook
This plugin allows you to update your local Git repositories in Redmine when changes have been pushed to Github.
== Description
Redmine <http://redmine.org> has supported Git repositories for a long time, allowing you to browse your code and view your changesets directly in Redmine. For this purpose, Redmine relies on local clones of the Git repositories.
If your shared repository is on a remote machine - for example on Github - this unfortunately means a bit of legwork to keep the local, Redmine-accessible repository up-to-date. The common approach is to set up a cronjob that pulls in any changes with regular intervals and updates Redmine with them.
That approach works perfectly fine, but is a bit heavy-handed and cumbersome. The Redmine Github Hook plugin allows Github to notify your Redmine installation when changes have been pushed to a repository, triggering an update of your local repository and Redmine data only when it is actually necessary.
== Installation
1. Installing the plugin
1. Install the json gem <http://json.rubyforge.org/> on the machine where Redmine is running.
2. Follow the plugin installation procedure at http://www.redmine.org/wiki/redmine/Plugins.
3. Restart your Redmine.
4. If you already have a local Git repository set up and working from Redmine go to step 3, otherwise continue at step 2.
2. Adding a Git repository to a project (note, this should work whether you want to use Redmine Github Hook or not). Either follow the instructions at http://www.redmine.org/wiki/redmine/HowTo_keep_in_sync_your_git_repository_for_redmine or the ones below:
1. Go to the directory on your Redmine machine where you want to keep your repository, for example /home/redmine/repositories/.
2. Get a clone of the repository into that location: git clone git://github.com/koppen/redmine_github_hook.git. This creates a .git directory at /home/redmine/repositories/redmine_github_hook/.git.
3. Open Redmine in your browser and navigate to the Settings for the project you want to add a Git repository to.
4. Under the Repository tab, choose Git as your SCM and enter the full path to the .git directory from step 2; /home/redmine/repositories/redmine_github_hook/.git/ . Click "Create".
5. Click the new "Repository" link in the main navigation to verify that your repository integration works - this might take a while as Redmine is fetching all commits.
3. Connecting Github to Redmine
1. Go to the repository Admin interface on Github.
2. Under "Service Hooks" add a new "Post-Receive URL" of the format: "[redmine_installation_url]/github_hook" (for example "http://example.com/github_hook").
1. By default, Github Hook assumes your Github repository name is the same as the project identifier in your Redmine installation. If this is not the case, you can specify the actual Redmine project identifier in the Post-Receive URL by using the format "[redmine_installation_url]/github_hook?project_id=[identifier]" (for example "http://example.com/github_hook?project_id=my_project").
That's it. Github will now send a HTTP POST to the Redmine Github Hook plugin whenever changes are pushed to Github. The plugin then takes care of pulling the changes to the local repository and updating the Redmine database with them.
== Assumptions
* Redmine 0.9 running on a *nix-like system. It will probably work on Redmine 0.8 as well.
* Git available on the commandline.
== License
Copyright (c) 2009 Jakob Skjerning
Permission is hereby granted, free of charge, to any person
obtaining a copy of this software and associated documentation
files (the "Software"), to deal in the Software without
restriction, including without limitation the rights to use,
copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following
conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.
require 'json'
require 'open3'
class GithubHookController < ApplicationController
skip_before_filter :verify_authenticity_token, :check_if_login_required
def index
repository = find_repository
# Fetch the changes from Github
update_repository(repository)
# Fetch the new changesets into Redmine
repository.fetch_changesets
render(:text => 'OK')
end
private
def exec(command)
logger.debug { "GithubHook: Executing command: '#{command}'" }
stdin, stdout, stderr = Open3.popen3(command)
output = stdout.readlines.collect(&:strip)
errors = stderr.readlines.collect(&:strip)
logger.debug { "GithubHook: Output from git:" }
logger.debug { "GithubHook: * STDOUT: #{output}"}
logger.debug { "GithubHook: * STDERR: #{output}"}
end
# Fetches updates from the remote repository
def update_repository(repository)
command = "cd '#{repository.url}' && git fetch origin && git reset --soft refs/remotes/origin/master"
exec(command)
end
# Gets the project identifier from the querystring parameters and if that's not supplied, assume
# the Github repository name is the same as the project identifier.
def get_identifier
payload = JSON.parse(params[:payload])
identifier = params[:project_id] || payload['repository']['name']
raise ActiveRecord::RecordNotFound, "Project identifier not specified" if identifier.nil?
return identifier
end
# Finds the Redmine project in the database based on the given project identifier
def find_project
identifier = get_identifier
project = Project.find_by_identifier(identifier.downcase)
raise ActiveRecord::RecordNotFound, "No project found with identifier '#{identifier}'" if project.nil?
return project
end
# Returns the Redmine Repository object we are trying to update
def find_repository
project = find_project
repository = project.repository
raise TypeError, "Project '#{project.to_s}' ('#{project.identifier}') has no repository" if repository.nil?
raise TypeError, "Repository for project '#{project.to_s}' ('#{project.identifier}') is not a Git repository" unless repository.is_a?(Repository::Git)
return repository
end
end
require 'redmine'
Redmine::Plugin.register :redmine_github_hook do
name 'Redmine Github Hook plugin'
author 'Jakob Skjerning'
description 'This plugin allows your Redmine installation to receive Github post-receive notifications'
version '0.1.1'
end
# English strings go here
my_label: "My label"
require File.dirname(__FILE__) + '/../test_helper'
require 'mocha'
class GithubHookControllerTest < ActionController::TestCase
def setup
# Sample JSON post from http://github.com/guides/post-receive-hooks
@json = '{
"before": "5aef35982fb2d34e9d9d4502f6ede1072793222d",
"repository": {
"url": "http://github.com/defunkt/github",
"name": "github",
"description": "You\'re lookin\' at it.",
"watchers": 5,
"forks": 2,
"private": 1,
"owner": {
"email": "chris@ozmm.org",
"name": "defunkt"
}
},
"commits": [
{
"id": "41a212ee83ca127e3c8cf465891ab7216a705f59",
"url": "http://github.com/defunkt/github/commit/41a212ee83ca127e3c8cf465891ab7216a705f59",
"author": {
"email": "chris@ozmm.org",
"name": "Chris Wanstrath"
},
"message": "okay i give in",
"timestamp": "2008-02-15T14:57:17-08:00",
"added": ["filepath.rb"]
},
{
"id": "de8251ff97ee194a289832576287d6f8ad74e3d0",
"url": "http://github.com/defunkt/github/commit/de8251ff97ee194a289832576287d6f8ad74e3d0",
"author": {
"email": "chris@ozmm.org",
"name": "Chris Wanstrath"
},
"message": "update pricing a tad",
"timestamp": "2008-02-15T14:36:34-08:00"
}
],
"after": "de8251ff97ee194a289832576287d6f8ad74e3d0",
"ref": "refs/heads/master"
}'
@repository = Repository::Git.new
@repository.stubs(:fetch_changesets).returns(true)
@project = Project.new
@project.stubs(:repository).returns(@repository)
Project.stubs(:find_by_identifier).with('github').returns(@project)
# Make sure we don't run actual commands in test
Open3.stubs(:popen3)
Repository.expects(:fetch_changesets).never
end
def mock_descriptor(kind, contents = [])
descriptor = mock(kind)
descriptor.expects(:readlines).returns(contents)
descriptor
end
def do_post(payload = nil)
payload = @json if payload.nil?
payload = payload.to_json if payload.is_a?(Hash)
post :index, :payload => payload
end
def test_should_use_the_repository_name_as_project_identifier
Project.expects(:find_by_identifier).with('github').returns(@project)
@controller.stubs(:exec).returns(true)
do_post
end
def test_should_update_the_repository_using_git_on_the_commandline
Project.expects(:find_by_identifier).with('github').returns(@project)
@controller.expects(:exec).returns(true)
do_post
end
def test_should_use_project_identifier_from_request
Project.expects(:find_by_identifier).with('redmine').returns(@project)
@controller.stubs(:exec).returns(true)
post :index, :project_id => 'redmine', :payload => @json
end
def test_should_downcase_identifier
# Redmine project identifiers are always downcase
Project.expects(:find_by_identifier).with('redmine').returns(@project)
@controller.stubs(:exec).returns(true)
post :index, :project_id => 'ReDmInE', :payload => @json
end
def test_should_render_ok_when_done
@controller.expects(:exec).returns(true)
do_post
assert_response :success
assert_equal 'OK', @response.body
end
def test_should_fetch_changesets_into_the_repository
@controller.expects(:exec).returns(true)
@repository.expects(:fetch_changesets).returns(true)
do_post
assert_response :success
assert_equal 'OK', @response.body
end
def test_should_return_404_if_project_identifier_not_given
assert_raises ActiveRecord::RecordNotFound do
do_post :repository => {}
end
end
def test_should_return_404_if_project_not_found
assert_raises ActiveRecord::RecordNotFound do
Project.expects(:find_by_identifier).with('foobar').returns(nil)
do_post :repository => {:name => 'foobar'}
end
end
def test_should_return_500_if_project_has_no_repository
assert_raises TypeError do
project = mock('project', :to_s => 'My Project', :identifier => 'github')
project.expects(:repository).returns(nil)
Project.expects(:find_by_identifier).with('github').returns(project)
do_post :repository => {:name => 'github'}
end
end
def test_should_return_500_if_repository_is_not_git
assert_raises TypeError do
project = mock('project', :to_s => 'My Project', :identifier => 'github')
repository = Repository::Subversion.new
project.expects(:repository).at_least(1).returns(repository)
Project.expects(:find_by_identifier).with('github').returns(project)
do_post :repository => {:name => 'github'}
end
end
def test_should_not_require_login
@controller.expects(:exec).returns(true)
@controller.expects(:check_if_login_required).never
do_post
end
def test_exec_should_log_output_from_git_as_debug
stdout = mock_descriptor('STDOUT', ["output 1\n", "output 2\n"])
stderr = mock_descriptor('STDERR', ["error 1\n", "error 2\n"])
Open3.expects(:popen3).returns(['STDIN', stdout, stderr])
@controller.logger.expects(:debug).at_least(4)
do_post
end
end
# Load the normal Rails helper
require File.expand_path(File.dirname(__FILE__) + '/../../../../test/test_helper')
# Ensure that we are using the temporary fixture path
Engines::Testing.set_fixture_path
Copyright (C) 2011 by Splendeo Innovación
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
== Redmine/Chiliproject Project Filtering Plugin
This plugin adds a "filtering text box" on the projects/index screen.
In addition to text-based search, it's possible to add additional filters (see below).
This plugin can be used in conjunction with the Featured Projects plugin ( https://github.com/splendeo/featured_projects )
If this plugin is detected, projects marked as "featured" will appear first, on a box, followed by the rest of the projects.
== Adding filters
Filters can be added via Custom Fields.
* Log-in as the main administrator
* Go to the custom fields view. You can do so via the Admin/Custom Fields menu, or just typing /custom_fields/ on the url bar.
* Select the "Projects" tab
* Add a new custom field.
* Choose the "list" type, and fill in the name and the list of values.
* Mark the custom field as Searchable
* Save the new field
That's it - a new filter should appear on the projects/index screen
== Removing filters from the view
By default, any fields created as above will be used as filters. If you wish to hide any searchable custom field of type "list" you can deactivate it on the plugin settings screen (Admin -> Plugins -> Configure)
== Installation
1. Copy the plugin directory into the vendor/plugins directory
2. Start Redmine
(This plugin does not require migrations)
Installed plugins are listed and can be configured from 'Admin -> Plugins' screen.
== Credits
Development of this plugin was financed by the Open Hardware Repository - www.ohwr.org
<p class='custom_field custom_field_<%= custom_field.id %>'>
<%= label_tag 'custom_field.name', l(custom_field.name, :default => custom_field.name) %>
<%= select_tag("custom_fields[#{custom_field.id}]",
options_from_collection_for_select(
[nil] + custom_field.possible_values,
'to_s',
'to_s',
@custom_fields[custom_field.id.to_s]
)
)
%>
</p>
<% if @featured_projects && @featured_projects.any? %>
<div class="box featured_projects">
<h3 class="icon icon-featured"><%=l(:project_filtering_featured_projects_label) %></h3>
<%= render_project_hierarchy_with_filtering(@featured_projects, @custom_fields, @question) %>
</div>
<% end %>
<%= render_project_hierarchy_with_filtering(@projects, @custom_fields, @question) %>
<% content_for :header_tags do %>
<%= auto_discovery_link_tag(:atom, {:action => 'index', :format => 'atom', :key => User.current.rss_key}) %>
<%= stylesheet_link_tag "redmine_project_filtering.css", :plugin => "redmine_project_filtering" %>
<% end %>
<div class="contextual">
<%= link_to(l(:label_project_new), {:controller => 'projects', :action => 'new'}, :class => 'icon icon-add') + ' |' if User.current.allowed_to?(:add_project, nil, :global => true) %>
<%= link_to(l(:label_issue_view_all), { :controller => 'issues' }) + ' |' if User.current.allowed_to?(:view_issues, nil, :global => true) %>
<%= link_to(l(:label_overall_spent_time), { :controller => 'time_entries' }) + ' |' if User.current.allowed_to?(:view_time_entries, nil, :global => true) %>
<%= link_to l(:label_overall_activity), { :controller => 'activities', :action => 'index' }%>
</div>
<h2><%=l(:label_project_plural)%></h2>
<% form_tag('/projects', :method => :get, :id => :project_filtering) do %>
<fieldset id="filters" class="collapsible">
<legend onclick="toggleFieldset(this);"><%= l(:label_filter_plural) %></legend>
<div>
<p class='q'>
<%= label_tag 'q', l('project_filtering_q_label') %>
<%= text_field_tag 'q', @question, :size => 30, :id => 'search-input' %>
</p>
<%= render :partial => 'custom_field', :collection => @custom_fields_used_for_project_filtering %>
<p class='buttons'><%= submit_tag( l(:button_send), :id => 'filter_button') -%></p>
</div>
</fieldset>
<% end %>
<%= javascript_tag "Field.focus('search-input');" %>
<%= javascript_tag "$('filter_button').hide();" %>
<%= observe_form( :project_filtering,
:frequency => 0.5,
:url => { :controller => :projects, :action => :index, :format => :js },
:method => :get
)
%>
<div id="projects">
<%= render :partial => 'filtered_projects' %>
</div>
<% if User.current.logged? %>
<p style="text-align:right;">
<span class="my-project"><%= l(:label_my_projects) %></span>
</p>
<% end %>
<% other_formats_links do |f| %>
<%= f.link_to 'Atom', :url => {:key => User.current.rss_key} %>
<% end %>
<% html_title(l(:label_project_plural)) -%>
xml.instruct!
xml.projects :type => 'array' do
@projects.each do |project|
xml.project do
xml.id project.id
xml.name project.name
xml.identifier project.identifier
xml.description project.description
xml.parent(:id => project.parent_id, :name => project.parent.name) unless project.parent.nil?
xml.custom_fields do
project.custom_field_values.each do |custom_value|
xml.custom_field custom_value.value, :id => custom_value.custom_field_id, :name => custom_value.custom_field.name
end
end unless project.custom_field_values.empty?
xml.created_on project.created_on
xml.updated_on project.updated_on
end
end
end
<p><%= l(:project_filtering_info) %></p>
<fieldset>
<%= content_tag(:legend, l(:project_filtering_text_filters)) %>
<% @project_custom_fields.each do |field| %>
<p>
<% plugin_settings = @settings['used_fields'] %>
<% dom_id = "settings[used_fields][#{field.id}]" %>
<% checked = plugin_settings.present? && plugin_settings[field.id.to_s].present? %>
<%= check_box_tag dom_id, 1, checked %>
<%= label_tag dom_id, field.name %>
</p>
<% end %>
</fieldset>
#project_filtering label { display: block; }
#project_filtering p { float: left; }
#project_filtering p.q { width: 30em; }
#project_filtering p.custom_field { width: 10em; }
#project_filtering p.buttons { clear: both; with: 100%; float: none; }
ul.filter_fields { padding: 0; }
ul.filter_fields li {
list-style-type: none;
display: inline;
margin: 0 10px 0 0;
}
.icon-featured { background-image: url(../images/ribbon-16x16.png); }
# English strings go here
en:
project_filtering_text_filters: "Active filters"
project_filtering_info: Use this window to deactivate any custom fields that you don't want to use for filtering.
project_filtering_q_label: "Textual search"
project_filtering_featured_projects_label: "Featured Projects"
button_send: "Send"
require 'redmine'
Dispatcher.to_prepare :redmine_project_filtering do
require_dependency 'redmine_project_filtering'
require_dependency 'redmine_project_filtering/with_custom_values'
require_dependency 'projects_helper'
ProjectsHelper.send(:include, RedmineProjectFiltering::Patches::ProjectsHelperPatch)
require_dependency 'custom_field'
CustomField.send(:include, RedmineProjectFiltering::Patches::CustomFieldPatch)
require_dependency 'project'
Project.send(:include, RedmineProjectFiltering::Patches::ProjectPatch)
require_dependency 'projects_controller'
ProjectsController.send(:include, RedmineProjectFiltering::Patches::ProjectsControllerPatch)
require_dependency 'settings_controller'
SettingsController.send(:include, RedmineProjectFiltering::Patches::SettingsControllerPatch)
end
# will not work on development mode
Redmine::Plugin.register :redmine_project_filtering do
name 'Redmine Project filtering plugin'
author 'Enrique García Cota'
url 'http://development.splendeo.es/projects/redm-project-filter'
author_url 'http://www.splendeo.es'
description 'Adds filtering capabilities to the the project/index page'
version '0.9.5'
settings :default => {'used_fields' => {}}, :partial => 'settings/redmine_project_filtering'
end
module RedmineProjectFiltering
# transforms a question and a list of custom fields into something that Project.search can process
def self.calculate_tokens(question, custom_fields=nil)
list = []
list << custom_fields.values if custom_fields.present?
list << question if question.present?
tokens = list.join(' ').scan(%r{((\s|^)"[\s\w]+"(\s|$)|\S+)})
tokens = tokens.collect{ |m| m.first.gsub(%r{(^\s*"\s*|\s*"\s*$)}, '') }
# tokens must be at least 2 characters long
tokens.select {|w| w.length > 1 }
end
end
module RedmineProjectFiltering
module Patches
module CustomFieldPatch
def self.included(base) # :nodoc:
base.send(:include, InstanceMethods)
base.class_eval do
unloadable
named_scope( :used_for_project_filtering, lambda do |*args|
used_field_setting = Setting['plugin_redmine_project_filtering']['used_fields'] || {}
used_fields = used_field_setting.keys.collect(&:to_i)
{ :conditions => [ "custom_fields.type = 'ProjectCustomField'
AND custom_fields.field_format = 'list'
AND custom_fields.id IN (?)", used_fields ] }
end)
named_scope( :usable_for_project_filtering, {
:conditions => {
:type => 'ProjectCustomField',
:field_format => 'list'
},
:order => 'custom_fields.position ASC'
})
after_create :configure_use_in_project_filtering
end
end
module InstanceMethods
def configure_use_in_project_filtering
if(self.type == 'ProjectCustomField' and field_format=='list')
plugin_settings = Setting[:plugin_redmine_project_filtering]
plugin_settings[:used_fields] ||= {}
plugin_settings[:used_fields][self.id.to_s] = "1"
Setting[:plugin_redmine_project_filtering] = plugin_settings
end
end
end
end
end
end
module RedmineProjectFiltering
module Patches
module ProjectPatch
def self.included(base)
base.send(:include, RedmineProjectFiltering::WithCustomValues)
base.extend ClassMethods
base.class_eval do
unloadable
end
end
module ClassMethods
def search_by_question(question)
if question.length > 1
search(RedmineProjectFiltering.calculate_tokens(question), nil, :all_words => true).first.sort_by(&:lft)
else
all(:order => 'lft')
end
end
end
end
end
end
module RedmineProjectFiltering
module Patches
module ProjectsControllerPatch
def self.included(base) # :nodoc:
base.send(:include, InstanceMethods)
base.class_eval do
unloadable
before_filter :calculate_custom_fields, :only => :index
before_filter :calculate_project_filtering_settings, :only => :index
alias_method_chain :index, :project_filtering
end
end
module InstanceMethods
def calculate_custom_fields
@custom_fields_used_for_project_filtering = CustomField.used_for_project_filtering
end
def calculate_project_filtering_settings
@project_filtering_settings = Setting[:plugin_redmine_project_filtering]
end
def index_with_project_filtering
respond_to do |format|
format.any(:html, :xml) {
calculate_filtered_projects
}
format.js {
calculate_filtered_projects
render :update do |page|
page.replace_html 'projects', :partial => 'filtered_projects'
end
}
format.atom {
projects = Project.visible.find(:all, :order => 'created_on DESC',
:limit => Setting.feeds_limit.to_i)
render_feed(projects, :title => "#{Setting.app_title}: #{l(:label_project_latest)}")
}
end
end
private
def calculate_filtered_projects
@question = (params[:q] || "").strip
@custom_fields = params[:custom_fields] || {}
@projects = Project.visible
unless @custom_fields.empty?
@projects = @projects.with_custom_values(params[:custom_fields])
end
@featured_projects = @projects.featured if Project.respond_to? :featured
@projects = @projects.search_by_question(@question)
@featured_projects = @featured_projects.search_by_question(@question) if @featured_projects
end
end
end
end
end
module RedmineProjectFiltering
module Patches
module ProjectsHelperPatch
def self.included(base) # :nodoc:
base.send(:include, InstanceMethods)
base.class_eval do
unloadable
end
end
module InstanceMethods
# Renders a tree of projects as a nested set of unordered lists
# The given collection may be a subset of the whole project tree
# (eg. some intermediate nodes are private and can not be seen)
def render_project_hierarchy_with_filtering(projects,custom_fields,question)
s = []
if projects.any?
tokens = RedmineProjectFiltering.calculate_tokens(question, custom_fields)
ancestors = []
original_project = @project
projects.each do |project|
# set the project environment to please macros.
@project = project
if (ancestors.empty? || project.is_descendant_of?(ancestors.last))
s << "<ul class='projects #{ ancestors.empty? ? 'root' : nil}'>"
else
ancestors.pop
s << "</li>"
while (ancestors.any? && !project.is_descendant_of?(ancestors.last))
ancestors.pop
s << "</ul></li>"
end
end
classes = (ancestors.empty? ? 'root' : 'child')
s << "<li class='#{classes}'><div class='#{classes}'>" +
link_to( highlight_tokens(project.name, tokens),
{:controller => 'projects', :action => 'show', :id => project},
:class => "project #{User.current.member_of?(project) ? 'my-project' : nil}"
)
s << "<ul class='filter_fields'>"
CustomField.usable_for_project_filtering.each do |field|
value_model = project.custom_value_for(field.id)
value = value_model.present? ? value_model.value : nil
s << "<li><b>#{field.name.humanize}:</b> #{highlight_tokens(value, tokens)}</li>" if value.present?
end
s << "</ul>"
s << "<div class='clear'></div>"
unless project.description.blank?
s << "<div class='wiki description'>"
s << "<b>#{ t(:field_description) }:</b>"
s << highlight_tokens(textilizable(project.short_description, :project => project), tokens)
s << "\n</div>"
end
s << "</div>"
ancestors << project
end
ancestors.size.times{ s << "</li></ul>" }
@project = original_project
end
s.join "\n"
end
private
# copied from search_helper. This one doesn't escape html or limit the text length
def highlight_tokens(text, tokens)
return text unless text && tokens && !tokens.empty?
re_tokens = tokens.collect {|t| Regexp.escape(t)}
regexp = Regexp.new "(#{re_tokens.join('|')})", Regexp::IGNORECASE
result = ''
text.split(regexp).each_with_index do |words, i|
words = words.mb_chars
if i.even?
result << words
else
t = (tokens.index(words.downcase) || 0) % 4
result << content_tag('span', words, :class => "highlight token-#{t}")
end
end
result
end
end
end
end
end
module RedmineProjectFiltering
module Patches
module SettingsControllerPatch
def self.included(base) # :nodoc:
base.send(:include, InstanceMethods)
base.class_eval do
unloadable
before_filter :calculate_custom_fields_usable_for_project_filtering, :only => :plugin
end
end
module InstanceMethods
def calculate_custom_fields_usable_for_project_filtering
if params[:id] == 'redmine_project_filtering'
@project_custom_fields = CustomField.usable_for_project_filtering
@settings = @settings.blank? ? {'used_fields' => []} : @settings
end
end
end
end
end
end
# adds a with_custom_values named scope to the model it is included in
module RedmineProjectFiltering
module WithCustomValues
def self.included(base) # :nodoc:
base.class_eval do
named_scope( :with_custom_values,
lambda do |*args|
fields = args.first
strings = []
values = []
joins = []
fields.each do|key, value|
if(value.present?)
table_name = "custom_values_filtering_on_#{key}"
strings << "(#{table_name}.custom_field_id = ? AND #{table_name}.value = ?)"
values << key.to_i
values << value
joins << "left join custom_values #{table_name} on #{table_name}.customized_id = #{base.table_name}.id"
end
end
if strings.length == 0
{ :conditions => true }
else
{ :joins => joins.join(' '),
:conditions => [strings.join(' AND '), *values]
}
end
end
)
end
end
end
end
= Redmine sympa plugin
== Installation
cd your/redmine/project
script/plugin install git://github.com/splendeo/redmine_sympa.git
rake db:migrate_plugins RAILS_ENV=production
Then you must restart your app. This is Web-server dependant. In Apache, you would do:
touch tmp/restart.txt
You should now be able to customize the plugin.
* Open your to your redmine site on a browser window
* Log-in as the administrator
* Navigate to Administration / Plugins / Redmine Sympa plugin / Configure
* Change settings on that plugin
== Addional considerations
Due to the way the plugin is built, the user running redmine must be able to perform a sudo without password.
class MailingListController < ApplicationController
unloadable
before_filter :find_project, :authorize, :only => [:show]
def show
end
private
def find_project
# @project variable must be set before calling the authorize filter
@sympa_address = "sympa@#{Setting.plugin_redmine_sympa['redmine_sympa_domain']}"
@project = Project.find(params[:project_id])
end
end
<% html_title l(:redmine_sympa_mailing_list_page_title, :name => @project.name) -%>
<h2> <%=h @project.name %> mailing list </h2>
<p>
<%=l(:redmine_sympa_mailing_list_page_intro) %>
<a href='mailto:<%= @project.sympa_mailing_list_address %>'><%= @project.sympa_mailing_list_address %></a>
</p>
<p>
<%=l(:redmine_sympa_mailing_list_page_admin_address) %> <a href='mailto:<%= @project.sympa_admin_address %>'><%= @project.sympa_admin_address %></a>
</p>
<p>
<%=l(:redmine_sympa_mailing_list_page_subscribe) %> <em>subscribe <%= @project.identifier %> Firstname Name</em>
</p>
<p>
<%=l(:redmine_sympa_mailing_list_page_unsubscribe ) %> <em>unsubscribe <%= @project.identifier %> </em>
<p>
<%=l(:redmine_sympa_mailing_list_page_subscription_web) %> <a href="<%= @project.sympa_url %>"><%= @project.sympa_url %></a>
</p>
<p>
<%=l(:redmine_sympa_mailing_list_page_archive_text) %>
<a href='<%= @project.sympa_archive_url %>'><%= @project.sympa_archive_url %></a>
</p>
<p>
<%= textilizable @project.sympa_info %>
</p>
<fieldset>
<%= content_tag(:legend, l(:redmine_sympa_text_settings_configuration)) %>
<%= content_tag(:p, l(:redmine_sympa_settings_help)) %>
<p>
<%= content_tag(:label, l(:redmine_sympa_setting_roles)) %>
<%-
roles = Role.all(:order => "position ASC")
selected = @settings['redmine_sympa_roles'].collect(&:to_i) unless @settings['redmine_sympa_roles'].blank?
selected ||= []
-%>
<% unless roles.empty? %>
<%=
select_tag("settings[redmine_sympa_roles]",
content_tag(:option, '') + options_from_collection_for_select(roles, :id, :name, selected ),
:multiple => true,
:size => 5)
%>
<% else %>
<%= link_to(l(:join_project_missing_roles), :controller => 'roles', :action => 'index') %>
<% end %>
</p>
<p>
<%= content_tag(:label, l(:redmine_sympa_setting_list_type)) %>
<%= text_field_tag("settings[redmine_sympa_list_type]", @settings['redmine_sympa_list_type'], :size=>60 ) %>
</p>
<p>
<%= content_tag(:label, l(:redmine_sympa_setting_domain)) %>
<%= text_field_tag("settings[redmine_sympa_domain]", @settings['redmine_sympa_domain'], :size=>60 ) %>
</p>
<p>
<%= content_tag(:label, l(:redmine_sympa_setting_path)) %>
<%= text_field_tag("settings[redmine_sympa_path]", @settings['redmine_sympa_path'], :size=>60 ) %>
</p>
<p>
<%= content_tag(:label, l(:redmine_sympa_setting_archive_url)) %>
<%= text_field_tag("settings[redmine_sympa_archive_url]", @settings['redmine_sympa_archive_url'], :size=>60 ) %>
</p>
<p>
<%= content_tag(:label, l(:redmine_sympa_setting_info_url)) %>
<%= text_field_tag("settings[redmine_sympa_info_url]", @settings['redmine_sympa_info_url'], :size=>60 ) %>
</p>
<p>
<%= content_tag(:label, l(:redmine_sympa_setting_log)) %>
<%= text_field_tag("settings[redmine_sympa_log]", @settings['redmine_sympa_log'], :size=>60 ) %>
</p>
</fieldset>
# English strings go here
en:
permission_view_mailing_lists: "View Mailing Lists"
redmine_sympa_text_settings_configuration: "Configuration"
redmine_sympa_settings_help: "Select which Role a Member should have in order to be able to manage Sympa lists. This is usually 'Manager'. The domain name is the part after @ in yourlist@yourdomain.com. The other two settings are required executable paths."
redmine_sympa_setting_roles: "Manager role"
redmine_sympa_missing_roles: "Add new roles"
redmine_sympa_setting_domain: "Domain name"
redmine_sympa_setting_path: "Sympa.pl path"
redmine_sympa_setting_log: "Sympa log"
redmine_sympa_setting_archive_url: "Archive url prefix"
redmine_sympa_setting_info_url: "Information page url prefix"
redmine_sympa_setting_list_type: "List type"
field_sympa_info: "Mailing list information"
redmine_sympa_mailing_list_page_title: "{{name}} mailing list"
redmine_sympa_mailing_list_page_intro: "This project's mailing list address is:"
redmine_sympa_mailing_list_page_admin_address: "The subscription / unsubscription email address is:"
redmine_sympa_mailing_list_page_subscribe: "In order to subscribe, please send an empty email to that adress, with the header:"
redmine_sympa_mailing_list_page_unsubscribe: "In order to unsubscribe, please send an empty email to that address containing the header:"
redmine_sympa_mailing_list_page_subscription_web: "You may also use sympa's web interface for subscribing/unsubscribing:"
redmine_sympa_mailing_list_page_archive_text: "The list archive is available in: "
# English strings go here
en:
permission_view_mailing_lists: "View Mailing Lists"
redmine_sympa_text_settings_configuration: "Configuration"
redmine_sympa_settings_help: "Select which Role a Member should have in order to be able to manage Sympa lists. This is usually 'Manager'. The domain name is the part after @ in yourlist@yourdomain.com. The other two settings are required executable paths."
redmine_sympa_setting_roles: "Manager role"
redmine_sympa_missing_roles: "Add new roles"
redmine_sympa_setting_domain: "Domain name"
redmine_sympa_setting_path: "Sympa.pl path"
redmine_sympa_setting_log: "Sympa log"
redmine_sympa_setting_archive_url: "Archive url prefix"
redmine_sympa_setting_info_url: "Information page url prefix"
redmine_sympa_setting_list_type: "List type"
field_sympa_info: "Mailing list information"
redmine_sympa_mailing_list_page_title: "{{name}} mailing list"
redmine_sympa_mailing_list_page_intro: "This project's mailing list address is:"
redmine_sympa_mailing_list_page_admin_address: "The subscription / unsubscription email address is:"
redmine_sympa_mailing_list_page_subscribe: "In order to subscribe, please send an empty email to that adress, with the header:"
redmine_sympa_mailing_list_page_unsubscribe: "In order to unsubscribe, please send an empty email to that address containing the header:"
redmine_sympa_mailing_list_page_subscription_web: "You may also use sympa's web interface for subscribing/unsubscribing:"
redmine_sympa_mailing_list_page_archive_text: "The list archive is available in: "
# English strings go here
en:
permission_view_mailing_lists: "View Mailing Lists"
redmine_sympa_text_settings_configuration: "Configuration"
redmine_sympa_settings_help: "Select which Role a Member should have in order to be able to manage Sympa lists. This is usually 'Manager'. The domain name is the part after @ in yourlist@yourdomain.com. The other two settings are required executable paths."
redmine_sympa_setting_roles: "Manager role"
redmine_sympa_missing_roles: "Add new roles"
redmine_sympa_setting_domain: "Domain name"
redmine_sympa_setting_path: "Sympa.pl path"
redmine_sympa_setting_log: "Sympa log"
redmine_sympa_setting_archive_url: "Archive url prefix"
redmine_sympa_setting_info_url: "Information page url prefix"
redmine_sympa_setting_list_type: "List type"
field_sympa_info: "Mailing list information"
redmine_sympa_mailing_list_page_title: "{{name}} mailing list"
redmine_sympa_mailing_list_page_intro: "This project's mailing list address is:"
redmine_sympa_mailing_list_page_admin_address: "The subscription / unsubscription email address is:"
redmine_sympa_mailing_list_page_subscribe: "In order to subscribe, please send an empty email to that adress, with the header:"
redmine_sympa_mailing_list_page_unsubscribe: "In order to unsubscribe, please send an empty email to that address containing the header:"
redmine_sympa_mailing_list_page_subscription_web: "You may also use sympa's web interface for subscribing/unsubscribing:"
redmine_sympa_mailing_list_page_archive_text: "The list archive is available in: "
# English strings go here
en:
permission_view_mailing_lists: "View Mailing Lists"
redmine_sympa_text_settings_configuration: "Configuration"
redmine_sympa_settings_help: "Select which Role a Member should have in order to be able to manage Sympa lists. This is usually 'Manager'. The domain name is the part after @ in yourlist@yourdomain.com. The other two settings are required executable paths."
redmine_sympa_setting_roles: "Manager role"
redmine_sympa_missing_roles: "Add new roles"
redmine_sympa_setting_domain: "Domain name"
redmine_sympa_setting_path: "Sympa.pl path"
redmine_sympa_setting_log: "Sympa log"
redmine_sympa_setting_archive_url: "Archive url prefix"
redmine_sympa_setting_info_url: "Information page url prefix"
redmine_sympa_setting_list_type: "List type"
field_sympa_info: "Mailing list information"
redmine_sympa_mailing_list_page_title: "{{name}} mailing list"
redmine_sympa_mailing_list_page_intro: "This project's mailing list address is:"
redmine_sympa_mailing_list_page_admin_address: "The subscription / unsubscription email address is:"
redmine_sympa_mailing_list_page_subscribe: "In order to subscribe, please send an empty email to that adress, with the header:"
redmine_sympa_mailing_list_page_unsubscribe: "In order to unsubscribe, please send an empty email to that address containing the header:"
redmine_sympa_mailing_list_page_subscription_web: "You may also use sympa's web interface for subscribing/unsubscribing:"
redmine_sympa_mailing_list_page_archive_text: "The list archive is available in: "
# English strings go here
en:
permission_view_mailing_lists: "View Mailing Lists"
redmine_sympa_text_settings_configuration: "Configuration"
redmine_sympa_settings_help: "Select which Role a Member should have in order to be able to manage Sympa lists. This is usually 'Manager'. The domain name is the part after @ in yourlist@yourdomain.com. The other two settings are required executable paths."
redmine_sympa_setting_roles: "Manager role"
redmine_sympa_missing_roles: "Add new roles"
redmine_sympa_setting_domain: "Domain name"
redmine_sympa_setting_path: "Sympa.pl path"
redmine_sympa_setting_log: "Sympa log"
redmine_sympa_setting_archive_url: "Archive url prefix"
redmine_sympa_setting_info_url: "Information page url prefix"
redmine_sympa_setting_list_type: "List type"
field_sympa_info: "Mailing list information"
redmine_sympa_mailing_list_page_title: "{{name}} mailing list"
redmine_sympa_mailing_list_page_intro: "This project's mailing list address is:"
redmine_sympa_mailing_list_page_admin_address: "The subscription / unsubscription email address is:"
redmine_sympa_mailing_list_page_subscribe: "In order to subscribe, please send an empty email to that adress, with the header:"
redmine_sympa_mailing_list_page_unsubscribe: "In order to unsubscribe, please send an empty email to that address containing the header:"
redmine_sympa_mailing_list_page_subscription_web: "You may also use sympa's web interface for subscribing/unsubscribing:"
redmine_sympa_mailing_list_page_archive_text: "The list archive is available in: "
# German strings go here
de:
permission_view_mailing_lists: "View Mailing Lists"
redmine_sympa_text_settings_configuration: "Configuration"
redmine_sympa_settings_help: "Select which Role a Member should have in order to be able to manage Sympa lists. This is usually 'Manager'. The domain name is the part after @ in yourlist@yourdomain.com. The other two settings are required executable paths."
redmine_sympa_setting_roles: "Manager role"
redmine_sympa_missing_roles: "Add new roles"
redmine_sympa_setting_domain: "Domain name"
redmine_sympa_setting_path: "Sympa.pl path"
redmine_sympa_setting_log: "Sympa log"
redmine_sympa_setting_archive_url: "Archive url prefix"
redmine_sympa_setting_info_url: "Information page url prefix"
redmine_sympa_setting_list_type: "List type"
field_sympa_info: "Mailing list information"
redmine_sympa_mailing_list_page_title: "{{name}} mailing list"
redmine_sympa_mailing_list_page_intro: "This project's mailing list address is:"
redmine_sympa_mailing_list_page_admin_address: "The subscription / unsubscription email address is:"
redmine_sympa_mailing_list_page_subscribe: "In order to subscribe, please send an empty email to that adress, with the header:"
redmine_sympa_mailing_list_page_unsubscribe: "In order to unsubscribe, please send an empty email to that address containing the header:"
redmine_sympa_mailing_list_page_subscription_web: "You may also use sympa's web interface for subscribing/unsubscribing:"
redmine_sympa_mailing_list_page_archive_text: "The list archive is available in: "
# English strings go here
en:
permission_view_mailing_lists: "View Mailing Lists"
redmine_sympa_text_settings_configuration: "Configuration"
redmine_sympa_settings_help: "Select which Role a Member should have in order to be able to manage Sympa lists. This is usually 'Manager'. The domain name is the part after @ in yourlist@yourdomain.com. The other two settings are required executable paths."
redmine_sympa_setting_roles: "Manager role"
redmine_sympa_missing_roles: "Add new roles"
redmine_sympa_setting_domain: "Domain name"
redmine_sympa_setting_path: "Sympa.pl path"
redmine_sympa_setting_log: "Sympa log"
redmine_sympa_setting_archive_url: "Archive url prefix"
redmine_sympa_setting_info_url: "Information page url prefix"
redmine_sympa_setting_list_type: "List type"
field_sympa_info: "Mailing list information"
redmine_sympa_mailing_list_page_title: "{{name}} mailing list"
redmine_sympa_mailing_list_page_intro: "This project's mailing list address is:"
redmine_sympa_mailing_list_page_admin_address: "The subscription / unsubscription email address is:"
redmine_sympa_mailing_list_page_subscribe: "In order to subscribe, please send an empty email to that adress, with the header:"
redmine_sympa_mailing_list_page_unsubscribe: "In order to unsubscribe, please send an empty email to that address containing the header:"
redmine_sympa_mailing_list_page_subscription_web: "You may also use sympa's web interface for subscribing/unsubscribing:"
redmine_sympa_mailing_list_page_archive_text: "The list archive is available in: "
# English strings go here
en:
permission_view_mailing_lists: "View Mailing Lists"
redmine_sympa_text_settings_configuration: "Configuration"
redmine_sympa_settings_help: "Select which Role a Member should have in order to be able to manage Sympa lists. This is usually 'Manager'. The domain name is the part after @ in yourlist@yourdomain.com. The other two settings are required executable paths."
redmine_sympa_setting_roles: "Manager role"
redmine_sympa_missing_roles: "Add new roles"
redmine_sympa_setting_domain: "Domain name"
redmine_sympa_setting_path: "Sympa.pl path"
redmine_sympa_setting_log: "Sympa log"
redmine_sympa_setting_archive_url: "Archive url prefix"
redmine_sympa_setting_info_url: "Information page url prefix"
redmine_sympa_setting_list_type: "List type"
field_sympa_info: "Mailing list information"
redmine_sympa_mailing_list_page_title: "{{name}} mailing list"
redmine_sympa_mailing_list_page_intro: "This project's mailing list address is:"
redmine_sympa_mailing_list_page_admin_address: "The subscription / unsubscription email address is:"
redmine_sympa_mailing_list_page_subscribe: "In order to subscribe, please send an empty email to that adress, with the header:"
redmine_sympa_mailing_list_page_unsubscribe: "In order to unsubscribe, please send an empty email to that address containing the header:"
redmine_sympa_mailing_list_page_subscription_web: "You may also use sympa's web interface for subscribing/unsubscribing:"
redmine_sympa_mailing_list_page_archive_text: "The list archive is available in: "
# English strings go here
en:
permission_view_mailing_lists: "View Mailing Lists"
redmine_sympa_text_settings_configuration: "Configuration"
redmine_sympa_settings_help: "Select which Role a Member should have in order to be able to manage Sympa lists. This is usually 'Manager'. The domain name is the part after @ in yourlist@yourdomain.com. The other two settings are required executable paths."
redmine_sympa_setting_roles: "Manager role"
redmine_sympa_missing_roles: "Add new roles"
redmine_sympa_setting_domain: "Domain name"
redmine_sympa_setting_path: "Sympa.pl path"
redmine_sympa_setting_log: "Sympa log"
redmine_sympa_setting_archive_url: "Archive url prefix"
redmine_sympa_setting_info_url: "Information page url prefix"
redmine_sympa_setting_list_type: "List type"
field_sympa_info: "Mailing list information"
redmine_sympa_mailing_list_page_title: "{{name}} mailing list"
redmine_sympa_mailing_list_page_intro: "This project's mailing list address is:"
redmine_sympa_mailing_list_page_admin_address: "The subscription / unsubscription email address is:"
redmine_sympa_mailing_list_page_subscribe: "In order to subscribe, please send an empty email to that adress, with the header:"
redmine_sympa_mailing_list_page_unsubscribe: "In order to unsubscribe, please send an empty email to that address containing the header:"
redmine_sympa_mailing_list_page_subscription_web: "You may also use sympa's web interface for subscribing/unsubscribing:"
redmine_sympa_mailing_list_page_archive_text: "The list archive is available in: "
# Spanish strings go here
es:
permission_view_mailing_lists: "Ver lista de correo"
redmine_sympa_text_settings_configuration: "Configuración"
redmine_sympa_settings_help: "Seleccione que Rol debería tener un Miembro para poder gestionar listas de correo de Sympa. Normalmente 'Manager'. El nombre de dominio es la parte que va tras la @ en tulista@tudominio.com. Las otras settings son los paths a los ejecutables de sympa."
redmine_sympa_setting_roles: "Rol de control"
redmine_sympa_missing_roles: "Añadir roles"
redmine_sympa_setting_domain: "Nombre de dominio"
redmine_sympa_setting_path: "Path a Sympa.pl"
redmine_sympa_setting_log: "Log de sympa"
redmine_sympa_setting_archive_url: "Prefijo de la url de los archivos"
redmine_sympa_setting_info_url: "Prefijo de la url de información"
redmine_sympa_setting_list_type: "Tipo de lista"
field_sympa_info: "Información de la lista"
redmine_sympa_mailing_list_page_title: "Lista de correo {{name}}"
redmine_sympa_mailing_list_page_intro: "La dirección de esta lista de correo es:"
redmine_sympa_mailing_list_page_admin_address: "La dirección de correo para suscribirse / cancelar subscripciones es:"
redmine_sympa_mailing_list_page_subscribe: "Para suscribirse, por favor envíe un email vacío a dicha dirección, con la cabecera:"
redmine_sympa_mailing_list_page_unsubscribe: "Para cancelar una subscripción, por favor envía un mail vacío a dicha dirección, con la cabecera:"
redmine_sympa_mailing_list_page_subscription_web: "También puede utilizar la interfaz web para gestionar su suscripción:"
redmine_sympa_mailing_list_page_archive_text: "El archivo de la lista está disponible en: "
# English strings go here
en:
permission_view_mailing_lists: "View Mailing Lists"
redmine_sympa_text_settings_configuration: "Configuration"
redmine_sympa_settings_help: "Select which Role a Member should have in order to be able to manage Sympa lists. This is usually 'Manager'. The domain name is the part after @ in yourlist@yourdomain.com. The other two settings are required executable paths."
redmine_sympa_setting_roles: "Manager role"
redmine_sympa_missing_roles: "Add new roles"
redmine_sympa_setting_domain: "Domain name"
redmine_sympa_setting_path: "Sympa.pl path"
redmine_sympa_setting_log: "Sympa log"
redmine_sympa_setting_archive_url: "Archive url prefix"
redmine_sympa_setting_info_url: "Information page url prefix"
redmine_sympa_setting_list_type: "List type"
field_sympa_info: "Mailing list information"
redmine_sympa_mailing_list_page_title: "{{name}} mailing list"
redmine_sympa_mailing_list_page_intro: "This project's mailing list address is:"
redmine_sympa_mailing_list_page_admin_address: "The subscription / unsubscription email address is:"
redmine_sympa_mailing_list_page_subscribe: "In order to subscribe, please send an empty email to that adress, with the header:"
redmine_sympa_mailing_list_page_unsubscribe: "In order to unsubscribe, please send an empty email to that address containing the header:"
redmine_sympa_mailing_list_page_subscription_web: "You may also use sympa's web interface for subscribing/unsubscribing:"
redmine_sympa_mailing_list_page_archive_text: "The list archive is available in: "
# English strings go here
en:
permission_view_mailing_lists: "View Mailing Lists"
redmine_sympa_text_settings_configuration: "Configuration"
redmine_sympa_settings_help: "Select which Role a Member should have in order to be able to manage Sympa lists. This is usually 'Manager'. The domain name is the part after @ in yourlist@yourdomain.com. The other two settings are required executable paths."
redmine_sympa_setting_roles: "Manager role"
redmine_sympa_missing_roles: "Add new roles"
redmine_sympa_setting_domain: "Domain name"
redmine_sympa_setting_path: "Sympa.pl path"
redmine_sympa_setting_log: "Sympa log"
redmine_sympa_setting_archive_url: "Archive url prefix"
redmine_sympa_setting_info_url: "Information page url prefix"
redmine_sympa_setting_list_type: "List type"
field_sympa_info: "Mailing list information"
redmine_sympa_mailing_list_page_title: "{{name}} mailing list"
redmine_sympa_mailing_list_page_intro: "This project's mailing list address is:"
redmine_sympa_mailing_list_page_admin_address: "The subscription / unsubscription email address is:"
redmine_sympa_mailing_list_page_subscribe: "In order to subscribe, please send an empty email to that adress, with the header:"
redmine_sympa_mailing_list_page_unsubscribe: "In order to unsubscribe, please send an empty email to that address containing the header:"
redmine_sympa_mailing_list_page_subscription_web: "You may also use sympa's web interface for subscribing/unsubscribing:"
redmine_sympa_mailing_list_page_archive_text: "The list archive is available in: "
# French strings go here
fr:
permission_view_mailing_lists: "View Mailing Lists"
redmine_sympa_text_settings_configuration: "Configuration"
redmine_sympa_settings_help: "Select which Role a Member should have in order to be able to manage Sympa lists. This is usually 'Manager'. The domain name is the part after @ in yourlist@yourdomain.com. The other two settings are required executable paths."
redmine_sympa_setting_roles: "Manager role"
redmine_sympa_missing_roles: "Add new roles"
redmine_sympa_setting_domain: "Domain name"
redmine_sympa_setting_path: "Sympa.pl path"
redmine_sympa_setting_log: "Sympa log"
redmine_sympa_setting_archive_url: "Archive url prefix"
redmine_sympa_setting_info_url: "Information page url prefix"
redmine_sympa_setting_list_type: "List type"
field_sympa_info: "Mailing list information"
redmine_sympa_mailing_list_page_title: "{{name}} mailing list"
redmine_sympa_mailing_list_page_intro: "This project's mailing list address is:"
redmine_sympa_mailing_list_page_admin_address: "The subscription / unsubscription email address is:"
redmine_sympa_mailing_list_page_subscribe: "In order to subscribe, please send an empty email to that adress, with the header:"
redmine_sympa_mailing_list_page_unsubscribe: "In order to unsubscribe, please send an empty email to that address containing the header:"
redmine_sympa_mailing_list_page_subscription_web: "You may also use sympa's web interface for subscribing/unsubscribing:"
redmine_sympa_mailing_list_page_archive_text: "The list archive is available in: "
# English strings go here
en:
permission_view_mailing_lists: "View Mailing Lists"
redmine_sympa_text_settings_configuration: "Configuration"
redmine_sympa_settings_help: "Select which Role a Member should have in order to be able to manage Sympa lists. This is usually 'Manager'. The domain name is the part after @ in yourlist@yourdomain.com. The other two settings are required executable paths."
redmine_sympa_setting_roles: "Manager role"
redmine_sympa_missing_roles: "Add new roles"
redmine_sympa_setting_domain: "Domain name"
redmine_sympa_setting_path: "Sympa.pl path"
redmine_sympa_setting_log: "Sympa log"
redmine_sympa_setting_archive_url: "Archive url prefix"
redmine_sympa_setting_info_url: "Information page url prefix"
redmine_sympa_setting_list_type: "List type"
field_sympa_info: "Mailing list information"
redmine_sympa_mailing_list_page_title: "{{name}} mailing list"
redmine_sympa_mailing_list_page_intro: "This project's mailing list address is:"
redmine_sympa_mailing_list_page_admin_address: "The subscription / unsubscription email address is:"
redmine_sympa_mailing_list_page_subscribe: "In order to subscribe, please send an empty email to that adress, with the header:"
redmine_sympa_mailing_list_page_unsubscribe: "In order to unsubscribe, please send an empty email to that address containing the header:"
redmine_sympa_mailing_list_page_subscription_web: "You may also use sympa's web interface for subscribing/unsubscribing:"
redmine_sympa_mailing_list_page_archive_text: "The list archive is available in: "
# English strings go here
en:
permission_view_mailing_lists: "View Mailing Lists"
redmine_sympa_text_settings_configuration: "Configuration"
redmine_sympa_settings_help: "Select which Role a Member should have in order to be able to manage Sympa lists. This is usually 'Manager'. The domain name is the part after @ in yourlist@yourdomain.com. The other two settings are required executable paths."
redmine_sympa_setting_roles: "Manager role"
redmine_sympa_missing_roles: "Add new roles"
redmine_sympa_setting_domain: "Domain name"
redmine_sympa_setting_path: "Sympa.pl path"
redmine_sympa_setting_log: "Sympa log"
redmine_sympa_setting_archive_url: "Archive url prefix"
redmine_sympa_setting_info_url: "Information page url prefix"
redmine_sympa_setting_list_type: "List type"
field_sympa_info: "Mailing list information"
redmine_sympa_mailing_list_page_title: "{{name}} mailing list"
redmine_sympa_mailing_list_page_intro: "This project's mailing list address is:"
redmine_sympa_mailing_list_page_admin_address: "The subscription / unsubscription email address is:"
redmine_sympa_mailing_list_page_subscribe: "In order to subscribe, please send an empty email to that adress, with the header:"
redmine_sympa_mailing_list_page_unsubscribe: "In order to unsubscribe, please send an empty email to that address containing the header:"
redmine_sympa_mailing_list_page_subscription_web: "You may also use sympa's web interface for subscribing/unsubscribing:"
redmine_sympa_mailing_list_page_archive_text: "The list archive is available in: "
# English strings go here
en:
permission_view_mailing_lists: "View Mailing Lists"
redmine_sympa_text_settings_configuration: "Configuration"
redmine_sympa_settings_help: "Select which Role a Member should have in order to be able to manage Sympa lists. This is usually 'Manager'. The domain name is the part after @ in yourlist@yourdomain.com. The other two settings are required executable paths."
redmine_sympa_setting_roles: "Manager role"
redmine_sympa_missing_roles: "Add new roles"
redmine_sympa_setting_domain: "Domain name"
redmine_sympa_setting_path: "Sympa.pl path"
redmine_sympa_setting_log: "Sympa log"
redmine_sympa_setting_archive_url: "Archive url prefix"
redmine_sympa_setting_info_url: "Information page url prefix"
redmine_sympa_setting_list_type: "List type"
field_sympa_info: "Mailing list information"
redmine_sympa_mailing_list_page_title: "{{name}} mailing list"
redmine_sympa_mailing_list_page_intro: "This project's mailing list address is:"
redmine_sympa_mailing_list_page_admin_address: "The subscription / unsubscription email address is:"
redmine_sympa_mailing_list_page_subscribe: "In order to subscribe, please send an empty email to that adress, with the header:"
redmine_sympa_mailing_list_page_unsubscribe: "In order to unsubscribe, please send an empty email to that address containing the header:"
redmine_sympa_mailing_list_page_subscription_web: "You may also use sympa's web interface for subscribing/unsubscribing:"
redmine_sympa_mailing_list_page_archive_text: "The list archive is available in: "
# English strings go here
en:
permission_view_mailing_lists: "View Mailing Lists"
redmine_sympa_text_settings_configuration: "Configuration"
redmine_sympa_settings_help: "Select which Role a Member should have in order to be able to manage Sympa lists. This is usually 'Manager'. The domain name is the part after @ in yourlist@yourdomain.com. The other two settings are required executable paths."
redmine_sympa_setting_roles: "Manager role"
redmine_sympa_missing_roles: "Add new roles"
redmine_sympa_setting_domain: "Domain name"
redmine_sympa_setting_path: "Sympa.pl path"
redmine_sympa_setting_log: "Sympa log"
redmine_sympa_setting_archive_url: "Archive url prefix"
redmine_sympa_setting_info_url: "Information page url prefix"
redmine_sympa_setting_list_type: "List type"
field_sympa_info: "Mailing list information"
redmine_sympa_mailing_list_page_title: "{{name}} mailing list"
redmine_sympa_mailing_list_page_intro: "This project's mailing list address is:"
redmine_sympa_mailing_list_page_admin_address: "The subscription / unsubscription email address is:"
redmine_sympa_mailing_list_page_subscribe: "In order to subscribe, please send an empty email to that adress, with the header:"
redmine_sympa_mailing_list_page_unsubscribe: "In order to unsubscribe, please send an empty email to that address containing the header:"
redmine_sympa_mailing_list_page_subscription_web: "You may also use sympa's web interface for subscribing/unsubscribing:"
redmine_sympa_mailing_list_page_archive_text: "The list archive is available in: "
# English strings go here
en:
permission_view_mailing_lists: "View Mailing Lists"
redmine_sympa_text_settings_configuration: "Configuration"
redmine_sympa_settings_help: "Select which Role a Member should have in order to be able to manage Sympa lists. This is usually 'Manager'. The domain name is the part after @ in yourlist@yourdomain.com. The other two settings are required executable paths."
redmine_sympa_setting_roles: "Manager role"
redmine_sympa_missing_roles: "Add new roles"
redmine_sympa_setting_domain: "Domain name"
redmine_sympa_setting_path: "Sympa.pl path"
redmine_sympa_setting_log: "Sympa log"
redmine_sympa_setting_archive_url: "Archive url prefix"
redmine_sympa_setting_info_url: "Information page url prefix"
redmine_sympa_setting_list_type: "List type"
field_sympa_info: "Mailing list information"
redmine_sympa_mailing_list_page_title: "{{name}} mailing list"
redmine_sympa_mailing_list_page_intro: "This project's mailing list address is:"
redmine_sympa_mailing_list_page_admin_address: "The subscription / unsubscription email address is:"
redmine_sympa_mailing_list_page_subscribe: "In order to subscribe, please send an empty email to that adress, with the header:"
redmine_sympa_mailing_list_page_unsubscribe: "In order to unsubscribe, please send an empty email to that address containing the header:"
redmine_sympa_mailing_list_page_subscription_web: "You may also use sympa's web interface for subscribing/unsubscribing:"
redmine_sympa_mailing_list_page_archive_text: "The list archive is available in: "
# English strings go here
en:
permission_view_mailing_lists: "View Mailing Lists"
redmine_sympa_text_settings_configuration: "Configuration"
redmine_sympa_settings_help: "Select which Role a Member should have in order to be able to manage Sympa lists. This is usually 'Manager'. The domain name is the part after @ in yourlist@yourdomain.com. The other two settings are required executable paths."
redmine_sympa_setting_roles: "Manager role"
redmine_sympa_missing_roles: "Add new roles"
redmine_sympa_setting_domain: "Domain name"
redmine_sympa_setting_path: "Sympa.pl path"
redmine_sympa_setting_log: "Sympa log"
redmine_sympa_setting_archive_url: "Archive url prefix"
redmine_sympa_setting_info_url: "Information page url prefix"
redmine_sympa_setting_list_type: "List type"
field_sympa_info: "Mailing list information"
redmine_sympa_mailing_list_page_title: "{{name}} mailing list"
redmine_sympa_mailing_list_page_intro: "This project's mailing list address is:"
redmine_sympa_mailing_list_page_admin_address: "The subscription / unsubscription email address is:"
redmine_sympa_mailing_list_page_subscribe: "In order to subscribe, please send an empty email to that adress, with the header:"
redmine_sympa_mailing_list_page_unsubscribe: "In order to unsubscribe, please send an empty email to that address containing the header:"
redmine_sympa_mailing_list_page_subscription_web: "You may also use sympa's web interface for subscribing/unsubscribing:"
redmine_sympa_mailing_list_page_archive_text: "The list archive is available in: "
# English strings go here
en:
permission_view_mailing_lists: "View Mailing Lists"
redmine_sympa_text_settings_configuration: "Configuration"
redmine_sympa_settings_help: "Select which Role a Member should have in order to be able to manage Sympa lists. This is usually 'Manager'. The domain name is the part after @ in yourlist@yourdomain.com. The other two settings are required executable paths."
redmine_sympa_setting_roles: "Manager role"
redmine_sympa_missing_roles: "Add new roles"
redmine_sympa_setting_domain: "Domain name"
redmine_sympa_setting_path: "Sympa.pl path"
redmine_sympa_setting_log: "Sympa log"
redmine_sympa_setting_archive_url: "Archive url prefix"
redmine_sympa_setting_info_url: "Information page url prefix"
redmine_sympa_setting_list_type: "List type"
field_sympa_info: "Mailing list information"
redmine_sympa_mailing_list_page_title: "{{name}} mailing list"
redmine_sympa_mailing_list_page_intro: "This project's mailing list address is:"
redmine_sympa_mailing_list_page_admin_address: "The subscription / unsubscription email address is:"
redmine_sympa_mailing_list_page_subscribe: "In order to subscribe, please send an empty email to that adress, with the header:"
redmine_sympa_mailing_list_page_unsubscribe: "In order to unsubscribe, please send an empty email to that address containing the header:"
redmine_sympa_mailing_list_page_subscription_web: "You may also use sympa's web interface for subscribing/unsubscribing:"
redmine_sympa_mailing_list_page_archive_text: "The list archive is available in: "
# English strings go here
en:
permission_view_mailing_lists: "View Mailing Lists"
redmine_sympa_text_settings_configuration: "Configuration"
redmine_sympa_settings_help: "Select which Role a Member should have in order to be able to manage Sympa lists. This is usually 'Manager'. The domain name is the part after @ in yourlist@yourdomain.com. The other two settings are required executable paths."
redmine_sympa_setting_roles: "Manager role"
redmine_sympa_missing_roles: "Add new roles"
redmine_sympa_setting_domain: "Domain name"
redmine_sympa_setting_path: "Sympa.pl path"
redmine_sympa_setting_log: "Sympa log"
redmine_sympa_setting_archive_url: "Archive url prefix"
redmine_sympa_setting_info_url: "Information page url prefix"
redmine_sympa_setting_list_type: "List type"
field_sympa_info: "Mailing list information"
redmine_sympa_mailing_list_page_title: "{{name}} mailing list"
redmine_sympa_mailing_list_page_intro: "This project's mailing list address is:"
redmine_sympa_mailing_list_page_admin_address: "The subscription / unsubscription email address is:"
redmine_sympa_mailing_list_page_subscribe: "In order to subscribe, please send an empty email to that adress, with the header:"
redmine_sympa_mailing_list_page_unsubscribe: "In order to unsubscribe, please send an empty email to that address containing the header:"
redmine_sympa_mailing_list_page_subscription_web: "You may also use sympa's web interface for subscribing/unsubscribing:"
redmine_sympa_mailing_list_page_archive_text: "The list archive is available in: "
# English strings go here
en:
permission_view_mailing_lists: "View Mailing Lists"
redmine_sympa_text_settings_configuration: "Configuration"
redmine_sympa_settings_help: "Select which Role a Member should have in order to be able to manage Sympa lists. This is usually 'Manager'. The domain name is the part after @ in yourlist@yourdomain.com. The other two settings are required executable paths."
redmine_sympa_setting_roles: "Manager role"
redmine_sympa_missing_roles: "Add new roles"
redmine_sympa_setting_domain: "Domain name"
redmine_sympa_setting_path: "Sympa.pl path"
redmine_sympa_setting_log: "Sympa log"
redmine_sympa_setting_archive_url: "Archive url prefix"
redmine_sympa_setting_info_url: "Information page url prefix"
redmine_sympa_setting_list_type: "List type"
field_sympa_info: "Mailing list information"
redmine_sympa_mailing_list_page_title: "{{name}} mailing list"
redmine_sympa_mailing_list_page_intro: "This project's mailing list address is:"
redmine_sympa_mailing_list_page_admin_address: "The subscription / unsubscription email address is:"
redmine_sympa_mailing_list_page_subscribe: "In order to subscribe, please send an empty email to that adress, with the header:"
redmine_sympa_mailing_list_page_unsubscribe: "In order to unsubscribe, please send an empty email to that address containing the header:"
redmine_sympa_mailing_list_page_subscription_web: "You may also use sympa's web interface for subscribing/unsubscribing:"
redmine_sympa_mailing_list_page_archive_text: "The list archive is available in: "
# English strings go here
en:
permission_view_mailing_lists: "View Mailing Lists"
redmine_sympa_text_settings_configuration: "Configuration"
redmine_sympa_settings_help: "Select which Role a Member should have in order to be able to manage Sympa lists. This is usually 'Manager'. The domain name is the part after @ in yourlist@yourdomain.com. The other two settings are required executable paths."
redmine_sympa_setting_roles: "Manager role"
redmine_sympa_missing_roles: "Add new roles"
redmine_sympa_setting_domain: "Domain name"
redmine_sympa_setting_path: "Sympa.pl path"
redmine_sympa_setting_log: "Sympa log"
redmine_sympa_setting_archive_url: "Archive url prefix"
redmine_sympa_setting_info_url: "Information page url prefix"
redmine_sympa_setting_list_type: "List type"
field_sympa_info: "Mailing list information"
redmine_sympa_mailing_list_page_title: "{{name}} mailing list"
redmine_sympa_mailing_list_page_intro: "This project's mailing list address is:"
redmine_sympa_mailing_list_page_admin_address: "The subscription / unsubscription email address is:"
redmine_sympa_mailing_list_page_subscribe: "In order to subscribe, please send an empty email to that adress, with the header:"
redmine_sympa_mailing_list_page_unsubscribe: "In order to unsubscribe, please send an empty email to that address containing the header:"
redmine_sympa_mailing_list_page_subscription_web: "You may also use sympa's web interface for subscribing/unsubscribing:"
redmine_sympa_mailing_list_page_archive_text: "The list archive is available in: "
# English strings go here
en:
permission_view_mailing_lists: "View Mailing Lists"
redmine_sympa_text_settings_configuration: "Configuration"
redmine_sympa_settings_help: "Select which Role a Member should have in order to be able to manage Sympa lists. This is usually 'Manager'. The domain name is the part after @ in yourlist@yourdomain.com. The other two settings are required executable paths."
redmine_sympa_setting_roles: "Manager role"
redmine_sympa_missing_roles: "Add new roles"
redmine_sympa_setting_domain: "Domain name"
redmine_sympa_setting_path: "Sympa.pl path"
redmine_sympa_setting_log: "Sympa log"
redmine_sympa_setting_archive_url: "Archive url prefix"
redmine_sympa_setting_info_url: "Information page url prefix"
redmine_sympa_setting_list_type: "List type"
field_sympa_info: "Mailing list information"
redmine_sympa_mailing_list_page_title: "{{name}} mailing list"
redmine_sympa_mailing_list_page_intro: "This project's mailing list address is:"
redmine_sympa_mailing_list_page_admin_address: "The subscription / unsubscription email address is:"
redmine_sympa_mailing_list_page_subscribe: "In order to subscribe, please send an empty email to that adress, with the header:"
redmine_sympa_mailing_list_page_unsubscribe: "In order to unsubscribe, please send an empty email to that address containing the header:"
redmine_sympa_mailing_list_page_subscription_web: "You may also use sympa's web interface for subscribing/unsubscribing:"
redmine_sympa_mailing_list_page_archive_text: "The list archive is available in: "
# English strings go here
en:
permission_view_mailing_lists: "View Mailing Lists"
redmine_sympa_text_settings_configuration: "Configuration"
redmine_sympa_settings_help: "Select which Role a Member should have in order to be able to manage Sympa lists. This is usually 'Manager'. The domain name is the part after @ in yourlist@yourdomain.com. The other two settings are required executable paths."
redmine_sympa_setting_roles: "Manager role"
redmine_sympa_missing_roles: "Add new roles"
redmine_sympa_setting_domain: "Domain name"
redmine_sympa_setting_path: "Sympa.pl path"
redmine_sympa_setting_log: "Sympa log"
redmine_sympa_setting_archive_url: "Archive url prefix"
redmine_sympa_setting_info_url: "Information page url prefix"
redmine_sympa_setting_list_type: "List type"
field_sympa_info: "Mailing list information"
redmine_sympa_mailing_list_page_title: "{{name}} mailing list"
redmine_sympa_mailing_list_page_intro: "This project's mailing list address is:"
redmine_sympa_mailing_list_page_admin_address: "The subscription / unsubscription email address is:"
redmine_sympa_mailing_list_page_subscribe: "In order to subscribe, please send an empty email to that adress, with the header:"
redmine_sympa_mailing_list_page_unsubscribe: "In order to unsubscribe, please send an empty email to that address containing the header:"
redmine_sympa_mailing_list_page_subscription_web: "You may also use sympa's web interface for subscribing/unsubscribing:"
redmine_sympa_mailing_list_page_archive_text: "The list archive is available in: "
# English strings go here
en:
permission_view_mailing_lists: "View Mailing Lists"
redmine_sympa_text_settings_configuration: "Configuration"
redmine_sympa_settings_help: "Select which Role a Member should have in order to be able to manage Sympa lists. This is usually 'Manager'. The domain name is the part after @ in yourlist@yourdomain.com. The other two settings are required executable paths."
redmine_sympa_setting_roles: "Manager role"
redmine_sympa_missing_roles: "Add new roles"
redmine_sympa_setting_domain: "Domain name"
redmine_sympa_setting_path: "Sympa.pl path"
redmine_sympa_setting_log: "Sympa log"
redmine_sympa_setting_archive_url: "Archive url prefix"
redmine_sympa_setting_info_url: "Information page url prefix"
redmine_sympa_setting_list_type: "List type"
field_sympa_info: "Mailing list information"
redmine_sympa_mailing_list_page_title: "{{name}} mailing list"
redmine_sympa_mailing_list_page_intro: "This project's mailing list address is:"
redmine_sympa_mailing_list_page_admin_address: "The subscription / unsubscription email address is:"
redmine_sympa_mailing_list_page_subscribe: "In order to subscribe, please send an empty email to that adress, with the header:"
redmine_sympa_mailing_list_page_unsubscribe: "In order to unsubscribe, please send an empty email to that address containing the header:"
redmine_sympa_mailing_list_page_subscription_web: "You may also use sympa's web interface for subscribing/unsubscribing:"
redmine_sympa_mailing_list_page_archive_text: "The list archive is available in: "
# English strings go here
en:
permission_view_mailing_lists: "View Mailing Lists"
redmine_sympa_text_settings_configuration: "Configuration"
redmine_sympa_settings_help: "Select which Role a Member should have in order to be able to manage Sympa lists. This is usually 'Manager'. The domain name is the part after @ in yourlist@yourdomain.com. The other two settings are required executable paths."
redmine_sympa_setting_roles: "Manager role"
redmine_sympa_missing_roles: "Add new roles"
redmine_sympa_setting_domain: "Domain name"
redmine_sympa_setting_path: "Sympa.pl path"
redmine_sympa_setting_log: "Sympa log"
redmine_sympa_setting_archive_url: "Archive url prefix"
redmine_sympa_setting_info_url: "Information page url prefix"
redmine_sympa_setting_list_type: "List type"
field_sympa_info: "Mailing list information"
redmine_sympa_mailing_list_page_title: "{{name}} mailing list"
redmine_sympa_mailing_list_page_intro: "This project's mailing list address is:"
redmine_sympa_mailing_list_page_admin_address: "The subscription / unsubscription email address is:"
redmine_sympa_mailing_list_page_subscribe: "In order to subscribe, please send an empty email to that adress, with the header:"
redmine_sympa_mailing_list_page_unsubscribe: "In order to unsubscribe, please send an empty email to that address containing the header:"
redmine_sympa_mailing_list_page_subscription_web: "You may also use sympa's web interface for subscribing/unsubscribing:"
redmine_sympa_mailing_list_page_archive_text: "The list archive is available in: "
# English strings go here
en:
permission_view_mailing_lists: "View Mailing Lists"
redmine_sympa_text_settings_configuration: "Configuration"
redmine_sympa_settings_help: "Select which Role a Member should have in order to be able to manage Sympa lists. This is usually 'Manager'. The domain name is the part after @ in yourlist@yourdomain.com. The other two settings are required executable paths."
redmine_sympa_setting_roles: "Manager role"
redmine_sympa_missing_roles: "Add new roles"
redmine_sympa_setting_domain: "Domain name"
redmine_sympa_setting_path: "Sympa.pl path"
redmine_sympa_setting_log: "Sympa log"
redmine_sympa_setting_archive_url: "Archive url prefix"
redmine_sympa_setting_info_url: "Information page url prefix"
redmine_sympa_setting_list_type: "List type"
field_sympa_info: "Mailing list information"
redmine_sympa_mailing_list_page_title: "{{name}} mailing list"
redmine_sympa_mailing_list_page_intro: "This project's mailing list address is:"
redmine_sympa_mailing_list_page_admin_address: "The subscription / unsubscription email address is:"
redmine_sympa_mailing_list_page_subscribe: "In order to subscribe, please send an empty email to that adress, with the header:"
redmine_sympa_mailing_list_page_unsubscribe: "In order to unsubscribe, please send an empty email to that address containing the header:"
redmine_sympa_mailing_list_page_subscription_web: "You may also use sympa's web interface for subscribing/unsubscribing:"
redmine_sympa_mailing_list_page_archive_text: "The list archive is available in: "
# English strings go here
en:
permission_view_mailing_lists: "View Mailing Lists"
redmine_sympa_text_settings_configuration: "Configuration"
redmine_sympa_settings_help: "Select which Role a Member should have in order to be able to manage Sympa lists. This is usually 'Manager'. The domain name is the part after @ in yourlist@yourdomain.com. The other two settings are required executable paths."
redmine_sympa_setting_roles: "Manager role"
redmine_sympa_missing_roles: "Add new roles"
redmine_sympa_setting_domain: "Domain name"
redmine_sympa_setting_path: "Sympa.pl path"
redmine_sympa_setting_log: "Sympa log"
redmine_sympa_setting_archive_url: "Archive url prefix"
redmine_sympa_setting_info_url: "Information page url prefix"
redmine_sympa_setting_list_type: "List type"
field_sympa_info: "Mailing list information"
redmine_sympa_mailing_list_page_title: "{{name}} mailing list"
redmine_sympa_mailing_list_page_intro: "This project's mailing list address is:"
redmine_sympa_mailing_list_page_admin_address: "The subscription / unsubscription email address is:"
redmine_sympa_mailing_list_page_subscribe: "In order to subscribe, please send an empty email to that adress, with the header:"
redmine_sympa_mailing_list_page_unsubscribe: "In order to unsubscribe, please send an empty email to that address containing the header:"
redmine_sympa_mailing_list_page_subscription_web: "You may also use sympa's web interface for subscribing/unsubscribing:"
redmine_sympa_mailing_list_page_archive_text: "The list archive is available in: "
# English strings go here
en:
permission_view_mailing_lists: "View Mailing Lists"
redmine_sympa_text_settings_configuration: "Configuration"
redmine_sympa_settings_help: "Select which Role a Member should have in order to be able to manage Sympa lists. This is usually 'Manager'. The domain name is the part after @ in yourlist@yourdomain.com. The other two settings are required executable paths."
redmine_sympa_setting_roles: "Manager role"
redmine_sympa_missing_roles: "Add new roles"
redmine_sympa_setting_domain: "Domain name"
redmine_sympa_setting_path: "Sympa.pl path"
redmine_sympa_setting_log: "Sympa log"
redmine_sympa_setting_archive_url: "Archive url prefix"
redmine_sympa_setting_info_url: "Information page url prefix"
redmine_sympa_setting_list_type: "List type"
field_sympa_info: "Mailing list information"
redmine_sympa_mailing_list_page_title: "{{name}} mailing list"
redmine_sympa_mailing_list_page_intro: "This project's mailing list address is:"
redmine_sympa_mailing_list_page_admin_address: "The subscription / unsubscription email address is:"
redmine_sympa_mailing_list_page_subscribe: "In order to subscribe, please send an empty email to that adress, with the header:"
redmine_sympa_mailing_list_page_unsubscribe: "In order to unsubscribe, please send an empty email to that address containing the header:"
redmine_sympa_mailing_list_page_subscription_web: "You may also use sympa's web interface for subscribing/unsubscribing:"
redmine_sympa_mailing_list_page_archive_text: "The list archive is available in: "
# English strings go here
en:
permission_view_mailing_lists: "View Mailing Lists"
redmine_sympa_text_settings_configuration: "Configuration"
redmine_sympa_settings_help: "Select which Role a Member should have in order to be able to manage Sympa lists. This is usually 'Manager'. The domain name is the part after @ in yourlist@yourdomain.com. The other two settings are required executable paths."
redmine_sympa_setting_roles: "Manager role"
redmine_sympa_missing_roles: "Add new roles"
redmine_sympa_setting_domain: "Domain name"
redmine_sympa_setting_path: "Sympa.pl path"
redmine_sympa_setting_log: "Sympa log"
redmine_sympa_setting_archive_url: "Archive url prefix"
redmine_sympa_setting_info_url: "Information page url prefix"
redmine_sympa_setting_list_type: "List type"
field_sympa_info: "Mailing list information"
redmine_sympa_mailing_list_page_title: "{{name}} mailing list"
redmine_sympa_mailing_list_page_intro: "This project's mailing list address is:"
redmine_sympa_mailing_list_page_admin_address: "The subscription / unsubscription email address is:"
redmine_sympa_mailing_list_page_subscribe: "In order to subscribe, please send an empty email to that adress, with the header:"
redmine_sympa_mailing_list_page_unsubscribe: "In order to unsubscribe, please send an empty email to that address containing the header:"
redmine_sympa_mailing_list_page_subscription_web: "You may also use sympa's web interface for subscribing/unsubscribing:"
redmine_sympa_mailing_list_page_archive_text: "The list archive is available in: "
# English strings go here
en:
permission_view_mailing_lists: "View Mailing Lists"
redmine_sympa_text_settings_configuration: "Configuration"
redmine_sympa_settings_help: "Select which Role a Member should have in order to be able to manage Sympa lists. This is usually 'Manager'. The domain name is the part after @ in yourlist@yourdomain.com. The other two settings are required executable paths."
redmine_sympa_setting_roles: "Manager role"
redmine_sympa_missing_roles: "Add new roles"
redmine_sympa_setting_domain: "Domain name"
redmine_sympa_setting_path: "Sympa.pl path"
redmine_sympa_setting_log: "Sympa log"
redmine_sympa_setting_archive_url: "Archive url prefix"
redmine_sympa_setting_info_url: "Information page url prefix"
redmine_sympa_setting_list_type: "List type"
field_sympa_info: "Mailing list information"
redmine_sympa_mailing_list_page_title: "{{name}} mailing list"
redmine_sympa_mailing_list_page_intro: "This project's mailing list address is:"
redmine_sympa_mailing_list_page_admin_address: "The subscription / unsubscription email address is:"
redmine_sympa_mailing_list_page_subscribe: "In order to subscribe, please send an empty email to that adress, with the header:"
redmine_sympa_mailing_list_page_unsubscribe: "In order to unsubscribe, please send an empty email to that address containing the header:"
redmine_sympa_mailing_list_page_subscription_web: "You may also use sympa's web interface for subscribing/unsubscribing:"
redmine_sympa_mailing_list_page_archive_text: "The list archive is available in: "
# English strings go here
en:
permission_view_mailing_lists: "View Mailing Lists"
redmine_sympa_text_settings_configuration: "Configuration"
redmine_sympa_settings_help: "Select which Role a Member should have in order to be able to manage Sympa lists. This is usually 'Manager'. The domain name is the part after @ in yourlist@yourdomain.com. The other two settings are required executable paths."
redmine_sympa_setting_roles: "Manager role"
redmine_sympa_missing_roles: "Add new roles"
redmine_sympa_setting_domain: "Domain name"
redmine_sympa_setting_path: "Sympa.pl path"
redmine_sympa_setting_log: "Sympa log"
redmine_sympa_setting_archive_url: "Archive url prefix"
redmine_sympa_setting_info_url: "Information page url prefix"
redmine_sympa_setting_list_type: "List type"
field_sympa_info: "Mailing list information"
redmine_sympa_mailing_list_page_title: "{{name}} mailing list"
redmine_sympa_mailing_list_page_intro: "This project's mailing list address is:"
redmine_sympa_mailing_list_page_admin_address: "The subscription / unsubscription email address is:"
redmine_sympa_mailing_list_page_subscribe: "In order to subscribe, please send an empty email to that adress, with the header:"
redmine_sympa_mailing_list_page_unsubscribe: "In order to unsubscribe, please send an empty email to that address containing the header:"
redmine_sympa_mailing_list_page_subscription_web: "You may also use sympa's web interface for subscribing/unsubscribing:"
redmine_sympa_mailing_list_page_archive_text: "The list archive is available in: "
# English strings go here
en:
permission_view_mailing_lists: "View Mailing Lists"
redmine_sympa_text_settings_configuration: "Configuration"
redmine_sympa_settings_help: "Select which Role a Member should have in order to be able to manage Sympa lists. This is usually 'Manager'. The domain name is the part after @ in yourlist@yourdomain.com. The other two settings are required executable paths."
redmine_sympa_setting_roles: "Manager role"
redmine_sympa_missing_roles: "Add new roles"
redmine_sympa_setting_domain: "Domain name"
redmine_sympa_setting_path: "Sympa.pl path"
redmine_sympa_setting_log: "Sympa log"
redmine_sympa_setting_archive_url: "Archive url prefix"
redmine_sympa_setting_info_url: "Information page url prefix"
redmine_sympa_setting_list_type: "List type"
field_sympa_info: "Mailing list information"
redmine_sympa_mailing_list_page_title: "{{name}} mailing list"
redmine_sympa_mailing_list_page_intro: "This project's mailing list address is:"
redmine_sympa_mailing_list_page_admin_address: "The subscription / unsubscription email address is:"
redmine_sympa_mailing_list_page_subscribe: "In order to subscribe, please send an empty email to that adress, with the header:"
redmine_sympa_mailing_list_page_unsubscribe: "In order to unsubscribe, please send an empty email to that address containing the header:"
redmine_sympa_mailing_list_page_subscription_web: "You may also use sympa's web interface for subscribing/unsubscribing:"
redmine_sympa_mailing_list_page_archive_text: "The list archive is available in: "
# English strings go here
en:
permission_view_mailing_lists: "View Mailing Lists"
redmine_sympa_text_settings_configuration: "Configuration"
redmine_sympa_settings_help: "Select which Role a Member should have in order to be able to manage Sympa lists. This is usually 'Manager'. The domain name is the part after @ in yourlist@yourdomain.com. The other two settings are required executable paths."
redmine_sympa_setting_roles: "Manager role"
redmine_sympa_missing_roles: "Add new roles"
redmine_sympa_setting_domain: "Domain name"
redmine_sympa_setting_path: "Sympa.pl path"
redmine_sympa_setting_log: "Sympa log"
redmine_sympa_setting_archive_url: "Archive url prefix"
redmine_sympa_setting_info_url: "Information page url prefix"
redmine_sympa_setting_list_type: "List type"
field_sympa_info: "Mailing list information"
redmine_sympa_mailing_list_page_title: "{{name}} mailing list"
redmine_sympa_mailing_list_page_intro: "This project's mailing list address is:"
redmine_sympa_mailing_list_page_admin_address: "The subscription / unsubscription email address is:"
redmine_sympa_mailing_list_page_subscribe: "In order to subscribe, please send an empty email to that adress, with the header:"
redmine_sympa_mailing_list_page_unsubscribe: "In order to unsubscribe, please send an empty email to that address containing the header:"
redmine_sympa_mailing_list_page_subscription_web: "You may also use sympa's web interface for subscribing/unsubscribing:"
redmine_sympa_mailing_list_page_archive_text: "The list archive is available in: "
# English strings go here
en:
permission_view_mailing_lists: "View Mailing Lists"
redmine_sympa_text_settings_configuration: "Configuration"
redmine_sympa_settings_help: "Select which Role a Member should have in order to be able to manage Sympa lists. This is usually 'Manager'. The domain name is the part after @ in yourlist@yourdomain.com. The other two settings are required executable paths."
redmine_sympa_setting_roles: "Manager role"
redmine_sympa_missing_roles: "Add new roles"
redmine_sympa_setting_domain: "Domain name"
redmine_sympa_setting_path: "Sympa.pl path"
redmine_sympa_setting_log: "Sympa log"
redmine_sympa_setting_archive_url: "Archive url prefix"
redmine_sympa_setting_info_url: "Information page url prefix"
redmine_sympa_setting_list_type: "List type"
field_sympa_info: "Mailing list information"
redmine_sympa_mailing_list_page_title: "{{name}} mailing list"
redmine_sympa_mailing_list_page_intro: "This project's mailing list address is:"
redmine_sympa_mailing_list_page_admin_address: "The subscription / unsubscription email address is:"
redmine_sympa_mailing_list_page_subscribe: "In order to subscribe, please send an empty email to that adress, with the header:"
redmine_sympa_mailing_list_page_unsubscribe: "In order to unsubscribe, please send an empty email to that address containing the header:"
redmine_sympa_mailing_list_page_subscription_web: "You may also use sympa's web interface for subscribing/unsubscribing:"
redmine_sympa_mailing_list_page_archive_text: "The list archive is available in: "
# English strings go here
en:
permission_view_mailing_lists: "View Mailing Lists"
redmine_sympa_text_settings_configuration: "Configuration"
redmine_sympa_settings_help: "Select which Role a Member should have in order to be able to manage Sympa lists. This is usually 'Manager'. The domain name is the part after @ in yourlist@yourdomain.com. The other two settings are required executable paths."
redmine_sympa_setting_roles: "Manager role"
redmine_sympa_missing_roles: "Add new roles"
redmine_sympa_setting_domain: "Domain name"
redmine_sympa_setting_path: "Sympa.pl path"
redmine_sympa_setting_log: "Sympa log"
redmine_sympa_setting_archive_url: "Archive url prefix"
redmine_sympa_setting_info_url: "Information page url prefix"
redmine_sympa_setting_list_type: "List type"
field_sympa_info: "Mailing list information"
redmine_sympa_mailing_list_page_title: "{{name}} mailing list"
redmine_sympa_mailing_list_page_intro: "This project's mailing list address is:"
redmine_sympa_mailing_list_page_admin_address: "The subscription / unsubscription email address is:"
redmine_sympa_mailing_list_page_subscribe: "In order to subscribe, please send an empty email to that adress, with the header:"
redmine_sympa_mailing_list_page_unsubscribe: "In order to unsubscribe, please send an empty email to that address containing the header:"
redmine_sympa_mailing_list_page_subscription_web: "You may also use sympa's web interface for subscribing/unsubscribing:"
redmine_sympa_mailing_list_page_archive_text: "The list archive is available in: "
# English strings go here
en:
permission_view_mailing_lists: "View Mailing Lists"
redmine_sympa_text_settings_configuration: "Configuration"
redmine_sympa_settings_help: "Select which Role a Member should have in order to be able to manage Sympa lists. This is usually 'Manager'. The domain name is the part after @ in yourlist@yourdomain.com. The other two settings are required executable paths."
redmine_sympa_setting_roles: "Manager role"
redmine_sympa_missing_roles: "Add new roles"
redmine_sympa_setting_domain: "Domain name"
redmine_sympa_setting_path: "Sympa.pl path"
redmine_sympa_setting_log: "Sympa log"
redmine_sympa_setting_archive_url: "Archive url prefix"
redmine_sympa_setting_info_url: "Information page url prefix"
redmine_sympa_setting_list_type: "List type"
field_sympa_info: "Mailing list information"
redmine_sympa_mailing_list_page_title: "{{name}} mailing list"
redmine_sympa_mailing_list_page_intro: "This project's mailing list address is:"
redmine_sympa_mailing_list_page_admin_address: "The subscription / unsubscription email address is:"
redmine_sympa_mailing_list_page_subscribe: "In order to subscribe, please send an empty email to that adress, with the header:"
redmine_sympa_mailing_list_page_unsubscribe: "In order to unsubscribe, please send an empty email to that address containing the header:"
redmine_sympa_mailing_list_page_subscription_web: "You may also use sympa's web interface for subscribing/unsubscribing:"
redmine_sympa_mailing_list_page_archive_text: "The list archive is available in: "
# English strings go here
en:
permission_view_mailing_lists: "View Mailing Lists"
redmine_sympa_text_settings_configuration: "Configuration"
redmine_sympa_settings_help: "Select which Role a Member should have in order to be able to manage Sympa lists. This is usually 'Manager'. The domain name is the part after @ in yourlist@yourdomain.com. The other two settings are required executable paths."
redmine_sympa_setting_roles: "Manager role"
redmine_sympa_missing_roles: "Add new roles"
redmine_sympa_setting_domain: "Domain name"
redmine_sympa_setting_path: "Sympa.pl path"
redmine_sympa_setting_log: "Sympa log"
redmine_sympa_setting_archive_url: "Archive url prefix"
redmine_sympa_setting_info_url: "Information page url prefix"
redmine_sympa_setting_list_type: "List type"
field_sympa_info: "Mailing list information"
redmine_sympa_mailing_list_page_title: "{{name}} mailing list"
redmine_sympa_mailing_list_page_intro: "This project's mailing list address is:"
redmine_sympa_mailing_list_page_admin_address: "The subscription / unsubscription email address is:"
redmine_sympa_mailing_list_page_subscribe: "In order to subscribe, please send an empty email to that adress, with the header:"
redmine_sympa_mailing_list_page_unsubscribe: "In order to unsubscribe, please send an empty email to that address containing the header:"
redmine_sympa_mailing_list_page_subscription_web: "You may also use sympa's web interface for subscribing/unsubscribing:"
redmine_sympa_mailing_list_page_archive_text: "The list archive is available in: "
# English strings go here
en:
permission_view_mailing_lists: "View Mailing Lists"
redmine_sympa_text_settings_configuration: "Configuration"
redmine_sympa_settings_help: "Select which Role a Member should have in order to be able to manage Sympa lists. This is usually 'Manager'. The domain name is the part after @ in yourlist@yourdomain.com. The other two settings are required executable paths."
redmine_sympa_setting_roles: "Manager role"
redmine_sympa_missing_roles: "Add new roles"
redmine_sympa_setting_domain: "Domain name"
redmine_sympa_setting_path: "Sympa.pl path"
redmine_sympa_setting_log: "Sympa log"
redmine_sympa_setting_archive_url: "Archive url prefix"
redmine_sympa_setting_info_url: "Information page url prefix"
redmine_sympa_setting_list_type: "List type"
field_sympa_info: "Mailing list information"
redmine_sympa_mailing_list_page_title: "{{name}} mailing list"
redmine_sympa_mailing_list_page_intro: "This project's mailing list address is:"
redmine_sympa_mailing_list_page_admin_address: "The subscription / unsubscription email address is:"
redmine_sympa_mailing_list_page_subscribe: "In order to subscribe, please send an empty email to that adress, with the header:"
redmine_sympa_mailing_list_page_unsubscribe: "In order to unsubscribe, please send an empty email to that address containing the header:"
redmine_sympa_mailing_list_page_subscription_web: "You may also use sympa's web interface for subscribing/unsubscribing:"
redmine_sympa_mailing_list_page_archive_text: "The list archive is available in: "
# English strings go here
en:
permission_view_mailing_lists: "View Mailing Lists"
redmine_sympa_text_settings_configuration: "Configuration"
redmine_sympa_settings_help: "Select which Role a Member should have in order to be able to manage Sympa lists. This is usually 'Manager'. The domain name is the part after @ in yourlist@yourdomain.com. The other two settings are required executable paths."
redmine_sympa_setting_roles: "Manager role"
redmine_sympa_missing_roles: "Add new roles"
redmine_sympa_setting_domain: "Domain name"
redmine_sympa_setting_path: "Sympa.pl path"
redmine_sympa_setting_log: "Sympa log"
redmine_sympa_setting_archive_url: "Archive url prefix"
redmine_sympa_setting_info_url: "Information page url prefix"
redmine_sympa_setting_list_type: "List type"
field_sympa_info: "Mailing list information"
redmine_sympa_mailing_list_page_title: "{{name}} mailing list"
redmine_sympa_mailing_list_page_intro: "This project's mailing list address is:"
redmine_sympa_mailing_list_page_admin_address: "The subscription / unsubscription email address is:"
redmine_sympa_mailing_list_page_subscribe: "In order to subscribe, please send an empty email to that adress, with the header:"
redmine_sympa_mailing_list_page_unsubscribe: "In order to unsubscribe, please send an empty email to that address containing the header:"
redmine_sympa_mailing_list_page_subscription_web: "You may also use sympa's web interface for subscribing/unsubscribing:"
redmine_sympa_mailing_list_page_archive_text: "The list archive is available in: "
# English strings go here
en:
permission_view_mailing_lists: "View Mailing Lists"
redmine_sympa_text_settings_configuration: "Configuration"
redmine_sympa_settings_help: "Select which Role a Member should have in order to be able to manage Sympa lists. This is usually 'Manager'. The domain name is the part after @ in yourlist@yourdomain.com. The other two settings are required executable paths."
redmine_sympa_setting_roles: "Manager role"
redmine_sympa_missing_roles: "Add new roles"
redmine_sympa_setting_domain: "Domain name"
redmine_sympa_setting_path: "Sympa.pl path"
redmine_sympa_setting_log: "Sympa log"
redmine_sympa_setting_archive_url: "Archive url prefix"
redmine_sympa_setting_info_url: "Information page url prefix"
redmine_sympa_setting_list_type: "List type"
field_sympa_info: "Mailing list information"
redmine_sympa_mailing_list_page_title: "{{name}} mailing list"
redmine_sympa_mailing_list_page_intro: "This project's mailing list address is:"
redmine_sympa_mailing_list_page_admin_address: "The subscription / unsubscription email address is:"
redmine_sympa_mailing_list_page_subscribe: "In order to subscribe, please send an empty email to that adress, with the header:"
redmine_sympa_mailing_list_page_unsubscribe: "In order to unsubscribe, please send an empty email to that address containing the header:"
redmine_sympa_mailing_list_page_subscription_web: "You may also use sympa's web interface for subscribing/unsubscribing:"
redmine_sympa_mailing_list_page_archive_text: "The list archive is available in: "
class AddSympaInfoToProjects < ActiveRecord::Migration
def self.up
add_column :projects, :sympa_info, :text
end
def self.down
remove_column :projects, :sympa_info
end
end
require 'redmine'
require 'redmine_sympa/hooks/project_hooks'
require 'dispatcher'
Dispatcher.to_prepare :redmine_sympa do
require_dependency 'project'
require_dependency 'enabled_module'
Project.send(:include, RedmineSympa::Patches::ProjectPatch)
EnabledModule.send(:include, RedmineSympa::Patches::EnabledModulePatch)
end
Redmine::Plugin.register :redmine_sympa do
name 'Redmine Sympa plugin'
author 'Enrique García Cota'
description 'Integrates Redmine with Sympa mailing lists.'
version '0.0.3'
settings({
:partial => 'settings/redmine_sympa',
:default => {
'redmine_sympa_roles' => [],
'redmine_sympa_domain' => 'yourdomain.com',
'redmine_sympa_archive_url' => 'http://localhost/wws/arc/',
'redmine_sympa_path' => '/home/sympa/bin/sympa.pl',
'redmine_sympa_log' => "#{Rails.root}/log/sympa.log",
'redmine_sympa_list_type' => 'discussion_list'
}
})
#project_module ensures that only the projects that have them 'active' will show them
project_module :sympa_mailing_list do
#declares that our "show" from MailingList controller is public
permission(:view_mailing_lists, {:mailing_list => [:show]}, :public => true)
end
#Creates an entry on the project menu for displaying the mailing list
menu :project_menu, :mailing_list, { :controller => 'mailing_list', :action => 'show' }, :caption => 'Mailing List', :after => :activity, :param => :project_id
end
require 'redmine_sympa/sympa_logger'
module RedmineSympa
module Actions
def self.execute_command(command)
RedmineSympa::SympaLogger.info(" executing #{command}")
system "sudo #{command} >> #{RedmineSympa::SympaLogger.path} 2>&1 &"
end
def self.get_sympa_path
Setting.plugin_redmine_sympa['redmine_sympa_path']
end
def self.get_domain
Setting.plugin_redmine_sympa['redmine_sympa_domain']
end
def self.create_list(project)
temp_file = File.open("#{Rails.root}/tmp/list#{project.identifier}", "w+")
File.chmod(0644, temp_file.path)
temp_file.print(project.sympa_mailing_list_xml_def)
temp_file.flush
RedmineSympa::SympaLogger.info "Creating mailing list for project #{project.identifier}"
execute_command("#{get_sympa_path} --create_list --robot #{get_domain} --input_file #{temp_file.path}")
end
def self.destroy_list(project)
RedmineSympa::SympaLogger.info "Destroying mailing list for project #{project.identifier}"
execute_command("#{get_sympa_path} --purge_list=#{project.identifier}@#{get_domain}")
end
end
end
module RedmineSympa
module Hooks
class ProjectHooks < Redmine::Hook::ViewListener
# :project
# :form
def view_projects_form(context={})
content = context[:form].text_area(:sympa_info, :rows => 5, :class => 'wiki-edit')
#FIXME: this doesn't work any more. Why?
#content += wikitoolbar_for(:project_sympa_info)
return content_tag(:p, content)
end
end
end
end
require 'redmine_sympa/actions'
module RedmineSympa
module Patches
module EnabledModulePatch
def self.included(base)
base.send(:include, InstanceMethods)
base.extend(ClassMethods)
base.class_eval do
unloadable # Send unloadable so it will not be unloaded in development
after_create :sympa_enable_module
before_destroy :sympa_disable_module
end
end
module ClassMethods
end
module InstanceMethods
def is_a_sympa_module?
return (self.name == 'sympa_mailing_list' ? true : false)
end
def sympa_enable_module
self.reload
if(self.is_a_sympa_module?)
RedmineSympa::SympaLogger.info("EnabledModule: Project #{self.project.identifier} needs a new mailing list. We must registers all its users, too.")
RedmineSympa::Actions.create_list(project)
end
end
def sympa_disable_module
if(self.is_a_sympa_module?)
RedmineSympa::SympaLogger.info("EnabledModule: Project #{self.project.identifier} doesn't need a mailing list any more")
RedmineSympa::Actions.destroy_list(project)
end
end
end
end
end
end
module RedmineSympa
module Patches
module ProjectPatch
def self.included(base)
base.send(:include, InstanceMethods)
base.extend(ClassMethods)
base.class_eval do
unloadable # Send unloadable so it will not be unloaded in development
end
end
module ClassMethods
end
module InstanceMethods
def has_sympa_mailing_list?
return self.module_enabled?(:sympa_mailing_list)
end
def sympa_mailing_list_address
"#{identifier}@#{Setting.plugin_redmine_sympa['redmine_sympa_domain']}"
end
def sympa_admin_address
"sympa@#{Setting.plugin_redmine_sympa['redmine_sympa_domain']}"
end
def sympa_archive_url
"#{Setting.plugin_redmine_sympa['redmine_sympa_archive_url']}#{identifier}"
end
def sympa_url
"#{Setting.plugin_redmine_sympa['redmine_sympa_info_url']}#{identifier}"
end
def sympa_admin_emails
roles = Setting.plugin_redmine_sympa['redmine_sympa_roles'].collect{|r| r.to_i}
emails= members.all(:conditions => ['role_id IN (?)', roles]).collect{|m| m.user.mail}
emails.push(User.find_by_admin(true).mail)
return emails
end
# returns the xml needed for defining a mailing list
def sympa_mailing_list_xml_def
owners = sympa_admin_emails.collect{|m| "<owner multiple='1'><email>#{m}</email></owner>"}
list_type = Setting.plugin_redmine_sympa['redmine_sympa_list_type']
return "<?xml version='1.0' ?>
<list>
<listname>#{identifier}</listname>
<type>#{list_type}</type>
<subject>#{name}</subject>
<description>#{description}</description>
<status>open</status>
<language>en_US</language>
<topic>Computing</topic>
#{owners.join(' ')}
</list>"
end
end
end
end
end
module RedmineSympa
module SympaLogger
def self.path
sympa_log = Setting.plugin_redmine_sympa['redmine_sympa_log']
end
def self.file
if(@file==nil) then
@file = File.open(self.path, 'a')
@file.sync = true
end
return @file
end
def self.getLogger
if @logger == nil
@logger = Logger.new(self.file)
end
return @logger
end
def self.clear()
if(@logger!=nil)
@logger.close()
@logger = nil
end
if(@file!=nil)
@file.close()
end
File.delete(self.path)
end
def self.debug(msg)
getLogger.debug(msg)
end
def self.info(msg)
getLogger.info(msg)
end
def self.warn(msg)
getLogger.warn(msg)
end
def self.error(msg)
getLogger.error(msg)
end
def self.fatal(msg)
getLogger.fatal(msg)
end
end
end
namespace :redmine_sympa do
desc "Create a project's mailing list, and subscribe all its users to it"
task :create_list => :environment do
require 'redmine_sympa/actions.rb'
project = Project.find(ENV["PROJECT_ID"]) or raise "PROJECT_ID missing or invalid"
puts "Creating mailing list for project #{project.identifier}"
RedmineSympa::SympaLogger.info("Rake: create_list")
RedmineSympa::Actions.create_list(project)
end
desc "Destroy a project's mailing list"
task :destroy_list => :environment do
require 'redmine_sympa/actions.rb'
project = Project.find(ENV["PROJECT_ID"]) or raise "PROJECT_ID missing or invalid"
puts "Destroying mailing list for project #{project.identifier}"
RedmineSympa::SympaLogger.info("Rake: destroy_list")
RedmineSympa::Actions.destroy_list(project)
end
end
require File.dirname(__FILE__) + '/../test_helper'
class MailingListControllerTest < ActionController::TestCase
# Replace this with your real tests.
def test_truth
assert true
end
end
# Load the normal Rails helper
require File.expand_path(File.dirname(__FILE__) + '/../../../../test/test_helper')
# Ensure that we are using the temporary fixture path
Engines::Testing.set_fixture_path
System Notifications is a Redmine plugin that will allow an Administrator to
send notification emails to recently logged in users.
Copyright (C) 2008 Eric Davis, Little Stream Software
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.
Thanks go to the following people for patches and contributions:
Eric Davis of Little Stream Software
- Project Maintainer
Peter Chester of Shane and Peter, Inc
- Project sponsership
Shane Pearlman of Shane and Peter, Inc
- Project sponsership
youngseok yi
- Korean translation
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.
<one line to give the program's name and a brief idea of what it does.>
Copyright (C) <year> <name of author>
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.
<signature of Ty Coon>, 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.
= System Notification Plugin
System Notifications is a Redmine plugin that will allow an Administrator to send notification emails to recently logged in users. This is useful to announce new features, downtime, or other system information.
== Features
* Send email to recently logged in users
* Ajax user selector to preview which users will receive the email
== Getting the plugin
A copy of the plugin can be downloaded from {Little Stream Software}[https://projects.littlestreamsoftware.com/projects/list_files/redmine-notify] or from {GitHub}[http://github.com/edavis10/redmine-system-notification-plugin/tree/master]
== Installation and Setup
1. Follow the Redmine plugin installation steps at: http://www.redmine.org/wiki/redmine/Plugins Make sure the plugin is installed to +vendor/plugins/system_notification_plugin+
2. Restart your Redmine web servers (e.g. mongrel, thin, mod_rails)
== Usage
To send an email:
1. Login to Redmine as an Administrator
2. Browse to the Administration panel
3. Select the System Notifications Panel
4. Pick which users to send the notification to
5. Enter the Subject and Body of the email
6. Click Send
== License
This plugin is licensed under the GNU GPL v2. See COPYRIGHT.txt and GPL.txt for details.
== Project help
If you need help you can contact the maintainer at his email address (See CREDITS.txt) or create an issue in the Bug Tracker. The bug tracker is located at https://projects.littlestreamsoftware.com
#!/usr/bin/env ruby
PROJECT_NAME = 'system_notification_plugin'
REDMINE_PROJECT_NAME = 'redmine-notify'
# ----------
require "fileutils"
require 'rubygems'
gem 'rspec'
gem 'rspec-rails'
Dir[File.expand_path(File.dirname(__FILE__)) + "/lib/tasks/**/*.rake"].sort.each { |ext| load ext }
# Modifided from the RSpec on Rails plugins
PLUGIN_ROOT = File.expand_path(File.dirname(__FILE__))
REDMINE_ROOT = File.expand_path(File.dirname(__FILE__) + '/../../../')
REDMINE_APP = File.expand_path(File.dirname(__FILE__) + '/../../../app')
REDMINE_LIB = File.expand_path(File.dirname(__FILE__) + '/../../../lib')
require 'rake'
require 'rake/clean'
require 'rake/rdoctask'
require 'spec/rake/spectask'
$:.unshift(REDMINE_ROOT + '/vendor/plugins/cucumber/lib')
require 'cucumber/rake/task'
CLEAN.include('**/semantic.cache', "**/#{PROJECT_NAME}.zip", "**/#{PROJECT_NAME}.tar.gz")
# No Database needed
spec_prereq = :noop
task :noop do
end
task :default => [:spec, :features]
task :stats => "spec:statsetup"
desc "Run all specs in spec directory (excluding plugin specs)"
Spec::Rake::SpecTask.new(:spec => spec_prereq) do |t|
t.spec_opts = ['--options', "\"#{PLUGIN_ROOT}/spec/spec.opts\""]
t.spec_files = FileList['spec/**/*_spec.rb']
end
namespace :spec do
desc "Run all specs in spec directory with RCov (excluding plugin specs)"
Spec::Rake::SpecTask.new(:rcov) do |t|
t.spec_opts = ['--options', "\"#{PLUGIN_ROOT}/spec/spec.opts\""]
t.spec_files = FileList['spec/**/*_spec.rb']
t.rcov = true
t.rcov_opts << ["--rails", "--sort=coverage", "--exclude '/var/lib/gems,spec,#{REDMINE_APP},#{REDMINE_LIB}'"]
end
desc "Print Specdoc for all specs (excluding plugin specs)"
Spec::Rake::SpecTask.new(:doc) do |t|
t.spec_opts = ["--format", "specdoc", "--dry-run"]
t.spec_files = FileList['spec/**/*_spec.rb']
end
desc "Print Specdoc for all specs as HTML (excluding plugin specs)"
Spec::Rake::SpecTask.new(:htmldoc) do |t|
t.spec_opts = ["--format", "html:doc/rspec_report.html", "--loadby", "mtime"]
t.spec_files = FileList['spec/**/*_spec.rb']
end
[:models, :controllers, :views, :helpers, :lib].each do |sub|
desc "Run the specs under spec/#{sub}"
Spec::Rake::SpecTask.new(sub => spec_prereq) do |t|
t.spec_opts = ['--options', "\"#{PLUGIN_ROOT}/spec/spec.opts\""]
t.spec_files = FileList["spec/#{sub}/**/*_spec.rb"]
end
end
end
# TODO: Requires webrat to be installed as a plugin....
Cucumber::Rake::Task.new(:features) do |t|
t.cucumber_opts = "--format pretty"
end
namespace :features do
Cucumber::Rake::Task.new(:rcov) do |t|
t.cucumber_opts = "--format pretty"
t.rcov = true
t.rcov_opts << ["--rails", "--sort=coverage", "--exclude '/var/lib/gems,spec,#{REDMINE_APP},#{REDMINE_LIB},step_definitions,features/support'"]
end
end
desc 'Generate documentation for the Budget plugin.'
Rake::RDocTask.new(:doc) do |rdoc|
rdoc.rdoc_dir = 'doc'
rdoc.title = PROJECT_NAME
rdoc.options << '--line-numbers' << '--inline-source'
rdoc.rdoc_files.include('README.rdoc')
rdoc.rdoc_files.include('lib/**/*.rb')
rdoc.rdoc_files.include('app/**/*.rb')
end
namespace :release do
desc "Create a zip archive"
task :zip => [:clean] do
sh "git archive --format=zip --prefix=#{PROJECT_NAME}/ HEAD > #{PROJECT_NAME}.zip"
end
desc "Create a tarball archive"
task :tarball => [:clean] do
sh "git archive --format=tar --prefix=#{PROJECT_NAME}/ HEAD | gzip > #{PROJECT_NAME}.tar.gz"
end
desc 'Uploads project documentation'
task :upload_doc => ['spec:rcov', :doc, 'spec:htmldoc'] do |t|
# TODO: Get rdoc working without frames
`scp -r doc/ dev.littlestreamsoftware.com:/home/websites/projects.littlestreamsoftware.com/shared/embedded_docs/#{REDMINE_PROJECT_NAME}/doc`
`scp -r coverage/ dev.littlestreamsoftware.com:/home/websites/projects.littlestreamsoftware.com/shared/embedded_docs/#{REDMINE_PROJECT_NAME}/coverage`
end
end
begin
require 'jeweler'
Jeweler::Tasks.new do |s|
s.name = "system_notification_plugin"
s.summary = "The System Notification plugin allows Administrators to send systemwide email notifications to specific users."
s.email = "edavis@littlestreamsoftware.com"
s.homepage = "https://projects.littlestreamsoftware.com/projects/TODO"
s.description = "The System Notification plugin allows Administrators to send systemwide email notifications to specific users."
s.authors = ["Eric Davis"]
s.rubyforge_project = "system_notification_plugin" # TODO
s.files = FileList[
"[A-Z]*",
"init.rb",
"rails/init.rb",
"{bin,generators,lib,test,app,assets,config,lang}/**/*",
'lib/jeweler/templates/.gitignore'
]
end
Jeweler::GemcutterTasks.new
Jeweler::RubyforgeTasks.new do |rubyforge|
rubyforge.doc_task = "rdoc"
end
rescue LoadError
puts "Jeweler, or one of its dependencies, is not available. Install it with: sudo gem install technicalpickles-jeweler -s http://gems.github.com"
end
class SystemNotificationController < ApplicationController
unloadable
layout 'base'
before_filter :require_admin
def index
@system_notification = SystemNotification.new
end
def create
@system_notification = SystemNotification.new(params[:system_notification])
if params[:system_notification][:time]
@system_notification.users = SystemNotification.users_since(params[:system_notification][:time],
{
:projects => params[:system_notification][:projects]
})
end
if @system_notification.deliver
flash[:notice] = "System Notification was successfully sent."
redirect_to :action => 'index'
else
flash[:error] = "System Notification was not sent."
render :action => 'index'
end
end
def users_since
if params[:system_notification] && params[:system_notification][:time] && !params[:system_notification][:time].empty?
@users = SystemNotification.users_since(params[:system_notification][:time],
{
:projects => params[:system_notification][:projects]
})
end
@users ||= []
respond_to do |format|
format.html { redirect_to :action => 'index' }
format.js { render :partial => 'users', :object => @users }
end
end
end
module SystemNotificationHelper
def system_notification_project_select
html = "<label for='system_notification_projects'>#{l(:label_project_plural)}</label>"
if self.respond_to?(:project_tree_options_for_select)
html << select_tag('system_notification[projects][]',
project_tree_options_for_select(Project.find(:all)),
:multiple => true, :size => 10, :id => "system_notification_projects")
else
html << select_tag('system_notification[projects][]',
options_from_collection_for_select(Project.find(:all), :id, :name),
:multiple => true, :size => 10, :id => "system_notification_projects")
end
return html
end
end
class SystemNotification
attr_accessor :time
attr_accessor :subject
attr_accessor :body
attr_accessor :users
attr_accessor :errors
if Redmine.const_defined?(:I18n)
include Redmine::I18n
else
include GLoc
end
def initialize(options = { })
self.errors = { }
self.users = options[:users] || []
self.subject = options[:subject]
self.body = options[:body]
end
def valid?
self.errors = { }
if self.subject.blank?
self.errors['subject'] = 'activerecord_error_blank'
end
if self.body.blank?
self.errors['body'] = 'activerecord_error_blank'
end
if self.users.empty?
self.errors['users'] = 'activerecord_error_empty'
end
return self.errors.empty?
end
def deliver
if self.valid?
SystemNotificationMailer.deliver_system_notification(self)
return true
else
return false
end
end
def self.times
{
:day => ll(current_language, :text_24_hours),
:week => ll(current_language, :text_1_week),
:month => ll(current_language, :text_1_month),
:this_year => ll(current_language, :text_this_year),
:all => ll(current_language, :text_all_time)
}
end
def self.users_since(time, filters = { })
if SystemNotification.times.include?(time.to_sym)
members = Member.find(:all, :include => :user, :conditions => self.conditions(time, filters))
return members.collect(&:user).uniq
else
return []
end
end
private
def self.time_frame(time)
case time.to_sym
when :day
1.day.ago
when :week
7.days.ago
when :month
7.month.ago
when :this_year
Time.parse('Jan 1 ' + Time.now.year.to_s)
else
nil
end
end
def self.conditions(time, filters = { })
c = ARCondition.new
c.add ["#{User.table_name}.status = ?", User::STATUS_ACTIVE]
c.add ["#{User.table_name}.last_login_on > (?)", time_frame(time)] unless time.to_sym == :all
c.add ["project_id IN (?)", filters[:projects]] if filters[:projects]
return c.conditions
end
end
class SystemNotificationMailer < Mailer
def system_notification(system_notification)
recipients system_notification.users.collect(&:mail)
subject system_notification.subject
from User.current.mail
content_type "multipart/alternative"
part "text/plain" do |p|
p.body = render_message("system_notification.erb", :body => system_notification.body)
end
part "text/html" do |p|
p.body = render_message("system_notification.text.html.erb", :body => system_notification.body)
end
end
end
<label><%= l(:field_users) %></label>
<% unless users.empty? %>
<div id="user_items">
<%= users.size -%> <%= "users".pluralize %>
<ul>
<% users.sort.each do |user| %>
<li><%= h user.name %></li>
<% end %>
</ul>
</div>
<% end %>
<h1><%= l(:system_notification)%></h1>
<% labelled_tabular_form_for :system_notification, @system_notification, {:url =>{:action => 'create'}, :html => {:id => 'system_notification'}} do |f| %>
<div class="box">
<p>
<%= f.select 'time', SystemNotification.times.invert.to_a.sort, :include_blank => true %>
</p>
<p>
<%= system_notification_project_select %>
</p>
<p>
<%= f.text_field 'subject', :size => 80 -%>
</p>
<p>
<%= f.text_area 'body', :class => 'wiki-edit', :rows => 10 -%>
</p>
<p id="user_list">
<%= render :partial => 'users', :object => @system_notification.users %>
</p>
</div>
<%= submit_tag l(:button_send) -%> <input type="reset" value="<%= l(:button_clear) -%>" onclick="$('user_items').innerHTML = '';" />
<% end -%>
<%= observe_field 'system_notification_time',
:url => { :action => :users_since },
:update => :user_list,
:with => "$('system_notification').serialize()"
%>
<%= observe_field 'system_notification_projects',
:url => { :action => :users_since },
:update => :user_list,
:with => "$('system_notification').serialize()"
%>
<%= wikitoolbar_for 'system_notification_body' %>
en:
system_notification: "System Notifications"
field_time: Users since
field_body: Body
field_users: Users
button_send: Send
text_24_hours: "24 hours"
text_1_week: "1 week"
text_1_month: "1 month"
text_this_year: "This year"
text_all_time: "All"
hu:
system_notification: "Rendszer értesítések"
field_time: Felhasználók óta
field_subject: Cím
field_body: Leírás
field_users: Felhasználók
button_send: Küldés
text_24_hours: "24 órája"
text_1_week: "1 hét"
text_1_month: "1 hónap"
text_this_year: "Ebben az évben"
text_all_time: "Bármikor"
ko:
system_notification: "System Notifications"
field_time: 다음기간 안에 접속한 사용자
field_body: 본문
field_users: 수신자
button_send: 메일 보내기
text_24_hours: "하루 (24 시간)"
text_1_week: " (1 주)"
text_1_month: " (1 달)"
text_this_year: " (1 년)"
text_all_time: "모두"
zh:
system_notification: "系统通知邮件"
field_time: 根据最后登录时间选择用户
field_body: 正文
field_users: 用户
button_send: 发送
text_24_hours: "24小时"
text_1_week: "1周"
text_1_month: "1个月"
text_this_year: "今年"
text_all_time: "全部"
require File.dirname(__FILE__) + "/rails/init"
system_notification: "System Notifications"
field_time: Users since
field_body: Body
field_users: Users
button_send: Send
text_24_hours: "24 hours"
text_1_week: "1 week"
text_1_month: "1 month"
text_this_year: "This year"
text_all_time: "All"
system_notification: "Rendszer értesítések"
field_time: Felhasználók óta
field_subject: Cím
field_body: Leírás
field_users: Felhasználók
button_send: Küldés
text_24_hours: "24 órája"
text_1_week: "1 hét"
text_1_month: "1 hónap"
text_this_year: "Ebben az évben"
text_all_time: "Bármikor"
system_notification: "System Notifications"
field_time: 다음기간 안에 접속한 사용자
field_body: 본문
field_users: 수신자
button_send: 메일 보내기
text_24_hours: "하루 (24 시간)"
text_1_week: " (1 주)"
text_1_month: " (1 달)"
text_this_year: " (1 년)"
text_all_time: "모두"
system_notification: "系统通知邮件"
field_time: 根据最后登录时间选择用户
field_body: 正文
field_users: 用户
button_send: 发送
text_24_hours: "24小时"
text_1_week: "1周"
text_1_month: "1个月"
text_this_year: "今年"
text_all_time: "全部"
require 'redmine'
Redmine::Plugin.register :system_notification_plugin do
name 'Redmine System Notification plugin'
author 'Eric Davis'
description 'The System Notification plugin allows Administrators to send systemwide email notifications to specific users.'
url 'https://projects.littlestreamsoftware.com/projects/redmine-notify'
author_url 'http://www.littlestreamsoftware.com'
version '0.2.0'
requires_redmine :version_or_higher => '0.8.0'
menu :admin_menu, :system_notification, { :controller => 'system_notification', :action => 'index'}, :caption => :system_notification
end
require File.dirname(__FILE__) + '/../spec_helper'
describe SystemNotificationController do
it 'should allow administrator access' do
admin = mock_model(User, :admin? => true, :logged? => true, :language => :en)
User.should_receive(:current).at_least(:once).and_return(admin)
get :index
response.should be_success
end
it 'should deny anonymous users' do
get :index
response.should_not be_success
end
it 'should deny non-administrator users' do
user = mock_model(User, :admin? => false, :logged? => true, :language => :en)
User.should_receive(:current).at_least(:once).and_return(user)
get :index
response.should_not be_success
end
end
describe SystemNotificationController,'#create with valid SystemNotification' do
before(:each) do
admin = mock_model(User, :admin? => true, :logged? => true, :language => :en)
User.stub!(:current).at_least(:once).and_return(admin)
user1 = mock_model(User)
user2 = mock_model(User)
@users = [user1, user2]
@system_notification = mock_model(SystemNotification, :subject => 'Test', :body => 'A notification', :time => 'week', :users => @users)
@system_notification.stub!(:deliver).and_return(true)
@system_notification.stub!(:users=)
SystemNotification.stub!(:new).and_return(@system_notification)
SystemNotification.stub!(:users_since).with('week', :projects => nil).and_return(@users)
end
it 'should redirect to the #index' do
post :create, :system_notification => { :subject => 'Test', :body => 'A notification', :time => 'week'}
response.should be_redirect
response.should redirect_to(:controller => 'system_notification', :action => 'index')
end
it 'should display a message to the user' do
post :create, :system_notification => { :subject => 'Test', :body => 'A notification', :time => 'week'}
flash[:notice].should match(/success/)
end
it 'should assign @system_notification for the view' do
post :create, :system_notification => { :subject => 'Test', :body => 'A notification', :time => 'week'}
assigns[:system_notification].should_not be_nil
end
it 'should get the users based on the Time' do
SystemNotification.should_receive(:users_since).with('week', :projects => nil).and_return(@users)
post :create, :system_notification => { :subject => 'Test', :body => 'A notification', :time => 'week'}
assigns[:system_notification].users.should eql(@users)
end
it 'should optionally add a project filter' do
SystemNotification.should_receive(:users_since).with('week', {:projects => ['10']}).and_return(@users)
post :create, :system_notification => { :subject => 'Test', :body => 'A notification', :time => 'week', :projects => ['10']}
end
it 'should deliver the SystemNotification' do
@system_notification.should_receive(:deliver).and_return(true)
post :create, :system_notification => { :subject => 'Test', :body => 'A notification', :time => 'week'}
end
end
describe SystemNotificationController,'#create with an invalid SystemNotification' do
before(:each) do
admin = mock_model(User, :admin? => true, :logged? => true, :language => :en)
User.stub!(:current).at_least(:once).and_return(admin)
user1 = mock_model(User)
user2 = mock_model(User)
@users = [user1, user2]
@system_notification = mock_model(SystemNotification, :subject => 'Test', :body => 'A notification', :time => 'week', :users => @users)
@system_notification.stub!(:deliver).and_return(false)
@system_notification.stub!(:users=)
SystemNotification.stub!(:new).and_return(@system_notification)
SystemNotification.stub!(:users_since).with('week', :projects => nil).and_return(@users)
end
it 'should display the #index' do
post :create, :system_notification => { :subject => 'Test', :body => 'A notification', :time => 'week'}
response.should be_success
response.should render_template('index')
end
it 'should display a message to the user' do
post :create, :system_notification => { :subject => 'Test', :body => 'A notification', :time => 'week'}
flash[:error].should match(/not/)
end
it 'should assign @system_notification for the view' do
post :create, :system_notification => { :subject => 'Test', :body => 'A notification', :time => 'week'}
assigns[:system_notification].should_not be_nil
end
end
describe SystemNotificationController,'#users_since using HTML posts' do
before(:each) do
admin = mock_model(User, :admin? => true, :logged? => true, :language => :en)
User.stub!(:current).at_least(:once).and_return(admin)
end
it 'should redirect to #index' do
post :users_since, :time => 'week'
response.should be_redirect
response.should redirect_to(:controller => 'system_notification', :action => 'index')
end
end
describe SystemNotificationController,'#users_since using JavaScript posts' do
def do_js_post(time='week', filters={})
request.env["HTTP_ACCEPT"] = "text/javascript"
post :users_since, {:system_notification => { :time => time}.merge(filters) }
end
before(:each) do
admin = mock_model(User, :admin? => true, :logged? => true, :language => :en)
User.stub!(:current).at_least(:once).and_return(admin)
end
it 'should respond to js requests' do
do_js_post
response.should be_success
end
it 'should render the user partial' do
do_js_post
response.should render_template('_users')
end
it 'should find the users since a time' do
SystemNotification.should_receive(:users_since).with('week', {:projects => nil}).and_return([])
do_js_post
end
it 'should optionally add a project filter' do
SystemNotification.should_receive(:users_since).with('week', {:projects => ['10']}).and_return([])
do_js_post('week', :projects => ['10'])
end
it 'should set @users for the view' do
do_js_post
assigns[:users].should_not be_nil
end
it 'should handle empty strings for the time' do
SystemNotification.should_not_receive(:users_since)
do_js_post('')
response.should be_success
end
it 'should handle nil strings for the time' do
SystemNotification.should_not_receive(:users_since)
do_js_post('')
response.should be_success
end
end
require File.dirname(__FILE__) + '/../spec_helper'
describe SystemNotificationMailer, 'system_notification' do
include SystemNotificationSpecHelper
before(:each) do
@user = mock_model(User, :name => 'Test user', :mail => 'admin@example.com', :pref => { })
User.stub!(:current).and_return(@user)
@system_notification = SystemNotification.new(valid_attributes)
@mail = SystemNotificationMailer.create_system_notification(@system_notification)
end
it 'should send to the users specified' do
@mail.bcc.should have(2).things
@mail.bcc.should include("user1@example.com")
@mail.bcc.should include("user2@example.com")
end
it 'should use the subject from the object' do
@mail.subject.should eql(@system_notification.subject)
end
it 'should use the body from the object' do
@mail.encoded.should match(/#{ Regexp.escape(@system_notification.body) }/)
end
it 'should render the textile content into HTML' do
@mail.encoded.should match(/<strong>textile<\/strong>/)
end
it 'should use the Current user as the reply to' do
@mail.from.should include(@user.mail)
end
end
require File.dirname(__FILE__) + '/../spec_helper'
describe SystemNotification do
it 'should initilize errors to an empty Hash' do
system_notification = SystemNotification.new
system_notification.errors.should be_an_instance_of(Hash)
system_notification.errors.should be_empty
end
it 'should initilize users to an empty Array' do
system_notification = SystemNotification.new
system_notification.users.should be_an_instance_of(Array)
system_notification.users.should be_empty
end
end
describe SystemNotification, "valid?" do
include SystemNotificationSpecHelper
it 'should be valid with the body, subject, and users' do
system_notification = SystemNotification.new(valid_attributes)
system_notification.valid?.should be_true
end
it 'should be invalid without a subject' do
system_notification = SystemNotification.new(valid_attributes.except(:subject))
system_notification.valid?.should be_false
end
it 'should be invalid without a body' do
system_notification = SystemNotification.new(valid_attributes.except(:body))
system_notification.valid?.should be_false
end
it 'should be invalid without any users' do
system_notification = SystemNotification.new(valid_attributes.except(:users))
system_notification.valid?.should be_false
end
end
describe SystemNotification, ".deliver" do
include SystemNotificationSpecHelper
it 'should not send if the object is invalid' do
system_notification = SystemNotification.new(valid_attributes)
system_notification.should_receive(:valid?).and_return(false)
system_notification.deliver.should be_false
end
it 'should send a SystemNotification Mail' do
system_notification = SystemNotification.new(valid_attributes)
system_notification.should_receive(:valid?).and_return(true)
SystemNotificationMailer.should_receive(:deliver_system_notification)
system_notification.deliver.should be_true
end
end
describe SystemNotification, ".users_since" do
it 'should return an array for a valid Time' do
users = SystemNotification.users_since('week')
users.should be_an_instance_of(Array)
end
it 'should return an empty array for an invalid Time' do
users = SystemNotification.users_since('invalid')
users.should be_empty
end
describe 'should return all users who have been active since' do
before(:each) do
@project1 = mock_model(Project)
@project2 = mock_model(Project)
@user1 = mock_model(User)
@user2 = mock_model(User)
@user3 = mock_model(User)
@user_list = [@user1, @user2, @user3]
@member1 = mock_model(Member, :user => @user1, :project => @project1)
@member2 = mock_model(Member, :user => @user1, :project => @project2)
@member3 = mock_model(Member, :user => @user2, :project => @project1)
@member4 = mock_model(Member, :user => @user3, :project => @project2)
@member_list = [@member1, @member2, @member3, @member4]
end
it 'a week ago' do
time = 7.days.ago
SystemNotification.should_receive(:time_frame).at_least(:once).and_return(time)
Member.should_receive(:find).with(:all,
:include => :user,
:conditions => ['1=1 AND (users.status = ?) AND (users.last_login_on > (?))', User::STATUS_ACTIVE, time]).
and_return(@member_list)
users = SystemNotification.users_since('week')
users.should eql(@user_list)
end
it 'all time' do
Member.should_receive(:find).with(:all, :include => :user, :conditions => ['1=1 AND (users.status = ?)',User::STATUS_ACTIVE]).and_return(@member_list)
users = SystemNotification.users_since('all')
users.should eql(@user_list)
end
end
describe 'should allow filtering of users' do
before(:each) do
@project1 = mock_model(Project)
@project2 = mock_model(Project)
@user1 = mock_model(User)
@user2 = mock_model(User)
@user3 = mock_model(User)
@member1 = mock_model(Member, :user => @user1, :project => @project1)
@member2 = mock_model(Member, :user => @user1, :project => @project2)
@member3 = mock_model(Member, :user => @user2, :project => @project1)
@member4 = mock_model(Member, :user => @user3, :project => @project2)
end
it 'based on project' do
time = 7.days.ago
projects = [@project1]
SystemNotification.should_receive(:time_frame).at_least(:once).and_return(time)
Member.should_receive(:find).
with(:all, {
:include => :user,
:conditions => ["1=1 AND (users.status = ?) AND (users.last_login_on > (?)) AND (project_id IN (?))",
User::STATUS_ACTIVE,
time,
projects]
}).
and_return([@member1, @member3])
users = SystemNotification.users_since('week', :projects => projects)
users.should eql([@user1, @user2])
end
end
end
require File.dirname(__FILE__) + '/spec_helper'
describe Class do
it "should be a class of Class" do
Class.class.should eql(Class)
end
end
--colour
--format
progress
--loadby
mtime
--reverse
--backtrace
\ No newline at end of file
# This file is copied to ~/spec when you run 'ruby script/generate rspec'
# from the project root directory.
ENV["RAILS_ENV"] = "test"
# Allows loading of an environment config based on the environment
redmine_root = ENV["REDMINE_ROOT"] || File.dirname(__FILE__) + "/../../../.."
require File.expand_path(redmine_root + "/config/environment")
require 'spec'
require 'spec/rails'
Spec::Runner.configure do |config|
# If you're not using ActiveRecord you should remove these
# lines, delete config/database.yml and disable :active_record
# in your config/boot.rb
config.use_transactional_fixtures = true
config.use_instantiated_fixtures = false
config.fixture_path = RAILS_ROOT + '/spec/fixtures/'
# == Fixtures
#
# You can declare fixtures for each example_group like this:
# describe "...." do
# fixtures :table_a, :table_b
#
# Alternatively, if you prefer to declare them only once, you can
# do so right here. Just uncomment the next line and replace the fixture
# names with your fixtures.
#
# config.global_fixtures = :table_a, :table_b
#
# If you declare global fixtures, be aware that they will be declared
# for all of your examples, even those that don't use them.
#
# == Mock Framework
#
# RSpec uses it's own mocking framework by default. If you prefer to
# use mocha, flexmock or RR, uncomment the appropriate line:
#
# config.mock_with :mocha
# config.mock_with :flexmock
# config.mock_with :rr
end
# require the entire app if we're running under coverage testing,
# so we measure 0% covered files in the report
#
# http://www.pervasivecode.com/blog/2008/05/16/making-rcov-measure-your-whole-rails-app-even-if-tests-miss-entire-source-files/
if defined?(Rcov)
all_app_files = Dir.glob('{app,lib}/**/*.rb')
all_app_files.each{|rb| require rb}
end
module SystemNotificationSpecHelper
def valid_attributes
user1 = mock_model(User, :mail => 'user1@example.com')
user2 = mock_model(User, :mail => 'user2@example.com')
return {
:body => 'a body with some *textile*\n\nAnd some extra lines',
:subject => 'a subject line',
:users => [user1, user2]
}
end
end
# Generated by jeweler
# DO NOT EDIT THIS FILE
# Instead, edit Jeweler::Tasks in Rakefile, and run `rake gemspec`
# -*- encoding: utf-8 -*-
Gem::Specification.new do |s|
s.name = %q{system_notification_plugin}
s.version = "0.2.0"
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
s.authors = ["Eric Davis"]
s.date = %q{2009-10-14}
s.description = %q{The System Notification plugin allows Administrators to send systemwide email notifications to specific users.}
s.email = %q{edavis@littlestreamsoftware.com}
s.extra_rdoc_files = [
"README.rdoc"
]
s.files = [
"COPYRIGHT.txt",
"CREDITS.txt",
"GPL.txt",
"README.rdoc",
"Rakefile",
"VERSION",
"app/controllers/system_notification_controller.rb",
"app/helpers/system_notification_helper.rb",
"app/models/system_notification.rb",
"app/models/system_notification_mailer.rb",
"app/views/system_notification/_users.html.erb",
"app/views/system_notification/index.html.erb",
"app/views/system_notification_mailer/system_notification.erb",
"app/views/system_notification_mailer/system_notification.text.html.erb",
"config/locales/en.yml",
"config/locales/hu.yml",
"config/locales/ko.yml",
"config/locales/zh.yml",
"init.rb",
"lang/en.yml",
"lang/hu.yml",
"lang/ko.yml",
"lang/zh.yml",
"lib/empty",
"rails/init.rb",
"test/test_helper.rb"
]
s.homepage = %q{https://projects.littlestreamsoftware.com/projects/TODO}
s.rdoc_options = ["--charset=UTF-8"]
s.require_paths = ["lib"]
s.rubyforge_project = %q{system_notification_plugin}
s.rubygems_version = %q{1.3.5}
s.summary = %q{The System Notification plugin allows Administrators to send systemwide email notifications to specific users.}
s.test_files = [
"spec/spec_helper.rb",
"spec/models/system_notification_mailer_spec.rb",
"spec/models/system_notification_spec.rb",
"spec/controllers/system_notification_controller_spec.rb",
"spec/sanity_spec.rb",
"test/test_helper.rb"
]
if s.respond_to? :specification_version then
current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
s.specification_version = 3
if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
else
end
else
end
end
# Load the normal Rails helper
require File.expand_path(File.dirname(__FILE__) + '/../../../../test/test_helper')
# Ensure that we are using the temporary fixture path
Engines::Testing.set_fixture_path
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment